Given a foo table, a bar table, and a foos_bars table, all three with id columns, the approach to getting bars with foos that the documentation would seem to imply is something like:
class Foo < ROM::Relation[:sql]
def with_foos_bars
qualified.inner_join(:foos_bars, foo_id: :id)
end
def with_bars
with_category_fixtures.qualified.inner_join(:categories, id: :bar_id)
end
end
However, #qualified only applies to the class, so this is actually just qualifying "Foo" twice, but we need to qualify at least two of the tables for a usable SQL query. The same seems to be the case for #prefix. Omitting #qualified and prefix simply leads to an ambiguous SQL query.
To clarify: the question is how does one join through a join table in Ruby Object Mapper?
You need to use symbol column names with Sequel naming conventions for now, so something like this:
class Foo < ROM::Relation[:sql]
def with_foos_bars
qualified.inner_join(
foos_bars, foos_bars__foo_id: foos__id
)
end
def with_bars
with_category_fixtures.qualified.inner_join(
:categories, categories__id: :foos_bars__bar_id
)
end
end
The plan is to provide new interfaces that would simplify that, although I gotta say this simple naming conventions has been working well for me. Having said that, there's definitely place for improvements here.
I hope this helps.
Related
I am currently writing a program for a banking administration system using Ruby. One of the capabilities of this system is that it can create a new account, accounts can be one of six types.
I have the following method in my controller to cater for this function:
def create_account(type, holder)
case type
when :current then CurrentAccount.new(holder, #account_number)
when :savings then SavingsAccount.new(holder, #account_number)
when :business then BusinessAccount.new(holder, #account_number)
when :ir then IRAccount.new(holder, #account_number)
when :smb then SMBAccount.new(holder, #account_number)
when :student then StudentAccount.new(holder, #account_number)
end
end
Each of these accounts inherits from a base account and will eventually contain individual attributes, e.g. Interest rate, overdraft, etc.
Although this is functional and delivers the required results it feels a bit lengthy. However I can't think of any obvious ways to refactor.
Any suggestions welcome...
I am assuming that at some point the system or end user is effectively selecting a text type and you need to convert that into a class to use. Otherwise you could write calling code that simply referred and instantiated the correct class.
You can make what you have cleaner by defining a mapping between the symbol type and the class. So you could do this in the scope of create_account:
ACCOUNT_CLASS_FOR = Hash[
current: CurrentAccount,
savings: SavingsAccount,
business: BusinessAccount,
ir: IRAccount,
smb: SMBAccount,
student: StudentAccount
]
def create_account(type, holder)
if account_class = ACCOUNT_CLASS_FOR[ type ]
account_class.new( holder, #account_number )
else
raise "Bad account type #{type}"
end
end
This has less repeated code, and makes the mapping between the symbol names and matching Ruby classes more explicit. If you need to apply or test the conversion elsewhere, you could make the constant available in a different scope without repeating yourself.
You can make this even cleaner by having each class know its own label e.g.
class CurrentAccount
def self.label
:current
end
end
Then you could have something like this:
ALLOWED_ACCOUNT_CLASSES = [CurrentAccount,SavingsAccount,BusinessAccount, # etc.
ACCOUNT_CLASS_FOR = Hash[
ALLOWED_ACCOUNT_CLASSES.map { |klass| [klass.label, klass] }
]
Note it's quite common practice to use the mis-spelled klass variable here to avoid clashing with Ruby's class keyword, but you could also just use account_class
Here's another way, but you would need the type to be accordingly named with the class (ie. :ir -> :i_r )
def create_account(type, holder)
Object.const_get(type.to_s.camelize + "Account").new(holder, #account_number)
end
Even if this one is shorter, I like Neil answer because it looks safer
I'm looking to define a method on one of my objects that will return a just one column of data from all of its child objects so long as another column in the same record meets certain conditions.
For instance if I have two objects
ParentObject
has_many: child_objects
#fields
name (string)
ChildObject
belongs_to: parent_object
#fields
name (string)
whitelisted_at (datetime)
I've read up that I can get a list of all child_object records for a parent_object based on a conditional specified using .where(). For instance in my controller I have code like so:
ParentObject.child_objects.where("whitelisted_at IS NOT NULL")
This gives me an active record associate like so:
#<ActiveRecord::AssociationRelation [
<ChildObject id: 1, name:"Susan", whitelisted_at: "2015-02-18 12:07:37">,
<ChildObject id: 1, name:"Simon", whitelisted_at: "2015-02-18 12:07:37">,
<ChildObject id: 1, name:"Sally", whitelisted_at: "2015-02-18 12:07:37">
]
I was looking how I would then filter through these to return an array of just names. Ideally i'd be able to run this all as a Model method so:
class ChainObject < ActiveRecord::Base
...
def whitelisted_names
#... outputs [Susan, Simon, Sally]
end
end
What would be the most concise and rails-y way of doing this. I thought about doing a .where() then an .each() and having a block method but that seems really cumbersome and I'm sure I'm just missing some smart ActiveRecord or Association method that could pluck an array of values from multiple hashes. I'm pouring over the APIdock but I think the problem is I don't know how to describe what I'm trying to do!
In your parent model you could use where.not and use the pluck method ActiveRecord gives you (props to Stefan - see pluck)
class ParentObject < ActiveRecord::Base
...
def whitelisted_names
child_objects.where.not(whitelisted_at: nil).pluck(:name)
end
end
I have a class that calls different suppliers to find if an item is available. How do I execute the class that each constant returns?
class ItemProvider
ADAPTER_ONE = Providers::ItemFromSupplierOne
ADAPTER_TWO = Providers::ItemFromSupplierTwo
def get_item(item)
id = ItemDetail.new(item)
%w(ADAPTER_ONE ADAPTER_TWO).each do |provider|
item_detail = provider.new(id)
break if item_detail.valid?
end
item_detail
end
Your problem is that you aren't making an array that contains the constants' values; you're making an array with the strings "ADAPTER_ONE" and "ADAPTER_TWO". The %w() syntax always makes an array of strings — it doesn't resolve variable names.
What you want is to change your get_item code to something like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].each do |provider|
item_detail = provider.new(id)
break item_detail if item_detail.valid?
end or nil # break automatically makes the block return the value you break with
end
As an aside, personally, I think I'd rewrite it like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].map {|provider| provider.new(id) }.find &:valid?
end
Yup you have an array of strings not constants but if you want to go down that road in using classes from strings well it will be nice if you look at http://blog.sidu.in/2008/02/loading-classes-from-strings-in-ruby.html#.UuGdmGQ1i2w .Maybe it is not directly related to your problem but it is a good read.
In Rails I would simply use:
MyModel.column_names
to access the tables column names, but I am enduring my first fumblings with Sinatra (in order to better learn Ruby) and need to get the column names of a table into an array.
Previously, I have iterated over the params hash to extract key, value pairs:
params.each do |key, value|
#manipulate keys and values
end
and know that using:
#mm = MyModel.new
#mm.each do ....
leads to an undefined method 'each' error.
logger.info #mm.inspect
logs something like:
#<MyModel #id=nil #start=nil #end=nil #yada=nil #created_at=nil #updated_at=nil #foreign_id=nil>
So, how do I get those blasted column names I can see so clearly into an array?
Thanks...
UPDATE
Thanks to Doon for pointing me in the right direction with Datamapper properties.
I ended up making a private method column_names:
def self.column_names
self.properties.collect {|column| column.name.to_s }
end
which works a treat
Are you looking for properties ?
http://rubydoc.info/gems/dm-core/1.1.0/DataMapper/Model/Property
For any Ruby object you have the methods instance_variables that returns the array of that object’s attributes. Is that too generic for you? I’m not familiar with DataMapper, there may be a specific method to do the equivalent of column_names in ActiveRecord.
If I have a model of type Foo that has many child records of type Bar, I'd like to be able to show a list of Foo records and show the number of child Bar records. So I have something like...
#foos.each do |foo|
puts foo.name
puts foo.bars.count
end
How can I avoid the N+1 problem on my aggregates? In other words, I don't want a new SELECT COUNT(*)... query for each row. I could simply create a SQL view and map it to a new Model, but is there a simpler approach?
DataMpper is fickle about these things so I'll give you a couple options to try that may work depending on what your real code looks like.
Just change count to size, i.e., puts foo.bars.size. DM's strategic eager loading can sometimes work with this approach.
Force an eager load before the #foos.each loop, and change count to size, e.g.
#foos = Foo.all(...)
#foos.bars.to_a
#foos.each do | foo |
puts foo.name
puts foo.bars.size
end
Issue a raw SQL query before your #foos.each loop that returns structs with foo ids and bar counts, #map those into a Hash by food id and get them inside the loop. (I've only had to resort to this level of nonsense once or twice, I'd recommend fiddling with #1 and 2 for bit before it.)