Reputation: 525
We have a model association that looks something like this:
class Example < ActiveRecord::Base
has_many :others, :order => 'others.rank'
end
The rank column is an integer type. The details of these particular models are not really important though as we have found the same problem with other has_many associations between other models.
We have also added to the Enumerable module:
module Enumerable
def method_missing(name)
super unless name.to_s[0..7] == 'collect_'
method = name.to_s[8..-1]
collect{|element| element.send(method)}
end
end
This adds a collect_id method that we can use to get an array of record ids from an array of ActiveRecord objects.
So if we use a normal ActiveRecord find :all, we get a nice array which we can then use collect_id on but if we use Example.others.collect_id, we get
NoMethodError: undefined method `collect_id' for #<Class:0x2aaaac0060a0>
Example.others.class returns "Array" so is it lying or confused?
Our solution thus far has been to use it this way:
Example.others.to_a.collect_id
This works but this seems a bit strange. Why would you have to do that?
We are on Ruby 1.8.7 and Rails 2.3.4
Upvotes: 2
Views: 3465
Reputation: 64363
Two possible solutions:
1) Extend a specific association:
class Example < ActiveRecord::Base
has_many :others, :order => 'others.rank' do
def method_missing(name)
super unless name.to_s[0..7] == 'collect_'
method = name.to_s[8..-1]
collect{|element| element.send(method)}
end
end
end
2) Add the extensions to a module to get a repeatable solution.
Rails provides an option to extend the association array.
module Collector
def method_missing(name)
super unless name.to_s[0..7] == 'collect_'
method = name.to_s[8..-1]
collect{|element| element.send(method)}
end
end
class Example < ActiveRecord::Base
has_many :others, :order => 'others.rank', :extend => Collector
end
Read the documentation for more details. Search for "Association Extensions" in the page to get to the relevant section.
Upvotes: 2
Reputation: 111
Model associations are proxies, not just simple Arrays.
Instead of example.others.all.collect_id
and your patch, I suggest you use example.others.all.map(&:id)
which is the standard Rails and Ruby >=1.8.7 way to collect a single attribute.
Upvotes: 6
Reputation: 14379
ActiveRecord associations lazily load the has_many records for performance reasons. For example, if you call example.others.count you don't need to load all of the records. Try adding this along side your patch to enumerable:
class ActiveRecord::Associations::AssociationCollection
def method_missing(name)
super unless name.to_s[0..7] == 'collect_'
load_target unless loaded?
method = name.to_s[8..-1]
@target.collect{|element| element.send(method)}
end
end
Upvotes: 3