Reputation: 93
I wrote a convenience ActiveRecord extension to delegate methods to a base object (based on multi-table inheritance)
class ActiveRecord::Base def self.acts_as(base) class_eval %Q{ def method_missing(method, *args, &blk) #{base}.send(method, *args, &blk) rescue NoMethodError super end } end end
I have a state class and a base class
# state class class MyState < ActiveRecord::Base belongs_to :my_object acts_as :my_object end # base class class MyObject < ActiveRecord::Base has_one :head, :class_name => 'MyState' has_one :tail, :class_name => 'MyState' end
When I tried this out, I found out that it doesn't work in some cases. More specifically,
> MyState.first.some_method_in_base nil > MyObject.first.tail.some_method_in_base NoMethodError: undefined method `some_method_in_base' for #<ActiveRecord::Associations::HasOneAssociation:0xABCDEFG>
Can anyone enlighten me as to why one works and the other doesn't?
Upvotes: 4
Views: 4643
Reputation: 8512
When you run MyObject.first.tail
the object that actually responds is an AssociationProxy class that
has most of the basic instance methods removed, and delegates # unknown methods to @target via method_missing
You can get more details about the proxy running:
MyObject.first.proxy_owner
MyObject.first.proxy_reflection
MyObject.first.proxy_target
If you look in the code, you can see that the AssociationProxy proxies the method some_method_in_base to your MyState class only if MyState responds to some_method_in_base as you can see in the code below.
private
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if load_target
if @target.respond_to?(method)
@target.send(method, *args, &block)
else
super
end
end
end
Therefore, the method_missing you have defined in the target class is never called.
Upvotes: 5