eden li
3/15/2007 6:03:00 AM
Ah, the magic of AssociationProxy. I ran into weirdness with this
when I was doing a case/when on the return value of a has_many in one
of my models.
On Mar 15, 11:04 am, Chad Lester <chad.les...@gmail.com> wrote:
> And similarly, why could binding an object to a method fail with a type
> error, even when the object's class method is reporting that it is of
> the correct class.
>
> Yes, it's possible that the object is overriding both "class" and
> "method", but I really don't think so. And even if you did, it doesn't
> explain how "bind" figures out that it's not really an Array.
Looking at the C source for bind(), it checks the object's underlying
class against the receiving class, and does so without going through
any of the re-defined methods for the given object. This allows it to
peek into the real class for the given object even if the object over-
rode Object#class.
AFAIK, there's no way to prevent bind() from doing this.
> I'd love to know what mechanism they used to achieve this magic. If it
> was me, I would have probably made a sub-class of Array. If not that,
> then I would have inserted my own find_all with an "extend" call. But
> that would also show up when you called method(:find_all). So they're
> doing something else. If push comes to shove, I can start pouring
> through the rails code... but ugh..
It actually, implicitly, does re-define both of these methods. Take a
look at lib/active_record/associations/association_proxy.rb
ActiveRecord associations are all implemented using a proxy of a real
array as returned by find(). The proxy undefs *all* instance methods
except for those that begin with underscores among some other
'important' methods. It then defines a method_missing method which
catches any undefined method references and passes them to the
underlying array.
If the association object defines any method that shares the same name
as the underlying array object, it'll be called instead of
method_missing which prevents the association proxy from handing it to
the underlying array.
So, in your case, the HasManyAssociation defines #find_all which
causes it to be called instead of method_missing...
AssociationProxy defines #target which allows you to retrieve the
underlying object:
>> Array.instance_method(:find_all).bind(foo.target)
=> #<Method: Array(Enumerable)#find_all>