Warren
Warren

Reputation: 525

An Array returned by a model association is not an Array?

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

Answers (4)

Harish Shetty
Harish Shetty

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

topherx
topherx

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

tilleryj
tilleryj

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

fl00r
fl00r

Reputation: 83680

You should use all

 example.others.all.collect_id

Upvotes: 0

Related Questions