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.

On Oct 9, 2006, at 9:43 AM, eden li wrote:

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

On Oct 9, 2006, at 10:34 AM, eden li wrote:

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

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

On Oct 9, 2006, at 2:32 PM, Xavier N. wrote:

I got something, ActiveRecord::Associations::AssociationProxy
method_missing called at all with a method inherited from Object.
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

Xavier N. 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)/ }

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.

On Oct 9, 2006, at 3:09 PM, eden li wrote:

=> true

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

Great!

eden li <eden.li@…> writes:

Xavier N. 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

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…