What collection type does the has_many macro create?

Can someone explain why you can't use the return value of an ActiveRecord::Base collection in a case() statement properly? Here's an example:

item = User.find_by_username('me').books

[#<Book:0xb771b4b0 ... snip

case item; when Array then 'this is an array'; else "not an array, but a(n) #{item.class}"; end

=> "not an array, but a(n) Array"

item === Array

=> true

Can someone explain what's going on here? I'm baffled. The workaround I've come up with is to insert

  item = item.to_a if item === Array

before the case statement, but that's sooo hacky.

Indeed, has_many and friends do not return an Array, they return a subclass of

   ActiveRecord::Associations::AssociationCollection

defined in

   active_record/associations/association_collection.rb

-- fxn

What sort of magic is going on to have it return Array as its class then? I'm still baffled about the following console output:

item.class

=> Array

item === Array

=> true

Xavier Noria wrote:

I got something, ActiveRecord::Associations::AssociationProxy redefines === and defines

   def method_missing(method, *args, &block)      load_target      @target.send(method, *args, &block)    end

I traced method_missing is called whenever object.assoc.class is called and delegates the call to @target, which is a true Array. That explains partially what we see, but I fail to understand why is method_missing called at all with a method inherited from Object.

-- fxn

Xavier Noria wrote:

I got something, ActiveRecord::Associations::AssociationProxy redefines === and defines

..snip..

Ah, I didn't think to look one class up. That explains it.

... I fail to understand why is method_missing called at all with a method inherited from Object.

I'm guessing it's this line (#7 of association_proxy.rb in rails 1.1.6):

  instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

Oh yes, there's this at the top of that class

   instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

which effectively removes Object#class and thus method_missing is triggered.

We are getting closer, but not yet explaining that === fails to match Array in the case statement.

-- fxn

It looks like Ruby doesn't even use the === operator in the case() statement:

irb(main):005:0> class C; def ===(k); puts "called=== with #{k.inspect}";true; end; end => nil irb(main):006:0> case C.new; when String; 'string'; else; 'not'; end => "not" irb(main):007:0> C.new===String called=== with String => true

Maybe this question is ready to be ported over to ruby-talk.

Xavier Noria wrote:

eden li <eden.li@...> writes:

Xavier Noria wrote: > I got something, ActiveRecord::Associations::AssociationProxy > redefines === and defines > > ..snip..

It's also useful to understand that a === b often isn't the same as b === a

irb(main):001:0> 1 === Fixnum => false irb(main):002:0> Fixnum === 1 => true

case 1; when Fixnum; end # Will actually compare Fixnum === 1

Great!

Ah, that's it. So AssociationProxy won't ever be able to intercept the === call in the case/when, thus never allowing it to match the "when Array" statement. That clears it up...

Gareth Adams wrote: