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: