views:

35

answers:

2

Given a collection of named Foos from ActiveRecord, why does Array#include? not seem to call Foo.== but yet index does?

class Foo < ActiveRecord::Base
  def ==(s)
    self.name == s
  end
end

class Bar < ActiveRecord::Base
  has_many :foos
end

bar.foos << Foo.new( :name => 'hmm' )

bar.foos.all.include?('hmm')  # does select all from db every time
=> true

bar.foos.include?('hmm') # does not go to db, but does not find the Foo!
=> false

bar.foos.index('hmm') # does not go to db, but does find the Foo[0] !
=> 0

bar.foos.index('eh') # no such object
=> nil

I understand shallow about the proxies, but (without a detour into the AR source) why is index seemingly behaving correctly but include? is not !?

Is this a bug in the proxy behavior, and/or is this behavior documented somewhere ?

Thanks.

A: 

This is because bar.foos doesn't return an ActiveRecord::Base object but returns an AssociationProxy (see association_proxy.rb).

I don't suggest you to redefine the == in the association proxy or you will alter the behavior for all your associations in your application.

Simone Carletti
A: 

Simone, that's not the answer I'm looking for (but I like the way you think ;-)

That prompts me though to read the documentation, er source code of association_proxy.rb (actually, AssociationCollection which emulates methods of Array on the collection returned by the association.)

Looking at AssociationCollection.include?

 File activerecord/lib/active_record/associations/association_collection.rb, line 332
      def include?(record)
        return false unless record.is_a?(@reflection.klass)
        load_target if @reflection.options[:finder_sql] && !loaded?
        return @target.include?(record) if loaded?
        exists?(record)
      end

Looks like the arg record is expected to be of type @reflection.klass whereas Array.include? takes an object and uses the == comparator defined on the objects of the array.

Well, this is not what I wanted from AR anyway. Since Enumerable.member? seems to work on the association collection, I'll just use that. I want to scan the cached collection, and not hit the database again. Maybe someone could explain how AssociationCollection is remapping member? ?

tribalvibes