Quantcast
Channel: Endless Ramblings
Viewing all articles
Browse latest Browse all 4

method_missing and respond_to?: doing it properly

$
0
0

Sometime ago while using method_missing to implement some functionality I got the weird behavior that it would work only most of the time but not always.
In retrospect it's now pretty obvious but in the heat of the moment it took me about half a day of investigation and talking before I figured it out.

What happened is that I did only half of the work.
I defined method_missing but I forgot to define respond_to? accordingly.
The result is that it worked when I called it directly on the instance, but failed if an association was involved.

To give an example, say you have a class like this:

classA< ActiveRecord::Base
defexample
true
end

defmethod_missing(method,*args)
if method.to_s=~/example/
example
else
super
end
end
end

Calling *example* directly on your instance works just fine.
>> A.new.my_example
=> true

>> A.create!.example_me?
=> true


All fine, but as soon as you get an association in the middle of things:

classB< ActiveRecord::Base
belongs_to:a
end

It just doesn't go well anymore:

>> b = B.new(:a=>A.new)
=> #<B id: nil, a_id: nil, created_at: nil, updated_at: nil>

>> b.a.example?
NoMethodError: undefined method `example?' for #<A id: nil, created_at: nil, updated_at: nil>
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_proxy.rb:220:in `method_missing'
from (irb):55

>> b = B.create!(:a=>A.create!)
=> #<B id: 4, a_id: 7, created_at: "2010-03-08 21:15:01", updated_at: "2010-03-08 21:15:01">
>> b.a.failing_example
NoMethodError: undefined method `failing_example' for #<ActiveRecord::Associations::BelongsToAssociation:0xb70491d0>
from /var/lib/gems/1.8/gems/activerecord-2.3.5/lib/active_record/associations/association_proxy.rb:220:in `method_missing'
from (irb):57


Now, this last error is a bit clearer but I don't remember running into it at the time.
If I had just followed the association_proxy:220 hint right away... ;)

What happens is that b.a doesn't return the instance but rather an AssociationProxy instance that provides ActiveRecord's extended functionality and this proxy relies on A#respond_to? to correctly forward method calls to the actual instance.

What I should have done is:

classA< ActiveRecord::Base
defexample
true
end

defmethod_missing(method,*args)
if method.to_s=~/example/
example
else
super
end
end

defrespond_to?(method, include_private =false)
if method.to_s=~/example/
true
else
super
end
end
end

>>B.create!(:a=>A.create!).a.example?
=> true
>>B.new(:a=>A.new).a.failing_example
=> true


There are some much better write-ups on this topic, if you want to read more:
Using method_missing and respond_to? to create dynamic methods
Solving the method_missing/respond_to? problem


Now, I must be honest here: what I was doing was a big code smell :)
It taught me the lesson to use method_missing properly and was even quite fun to debug and all that but what I really needed and end up doing in that case was a group of delegates here and there and voilà, it was all cool and clean.

Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles





Latest Images