Battle of the method_missings

07 Mar 2007

So if you’ve heard about Ruby, you’ve probably heard about method_missing (short summary: if an object receives a message it doesn’t understand it calls method_missing which you can overwrite. This allows Rails ActiveRecord to handle User.find_by_email_and_name_and_phone_number on the fly – it parses the message and gives you the method you want dynamically). But when too many things try to use it at once you get some interesting problems. At some point RSpec enabled calling object_instance.should_include(‘thing’) by using method_missing on Object to catch any message that started with should_. So, in the previous example, RSpec would look for a method called include? on the object_instance, pass ‘thing’ into that, and tell you about it if it failed. Which is pretty cool because instead of saying:

assert_equal [‘a’, ‘b’, ‘c’], object_instance.some_method

you could say:

object_instance.should_eql [‘a’, ‘b’, ‘c’]

which is more in line with ruby’s quest for code that looks like natural language.

But, when you use RSpec with Rails the method_missings fight. RSpec overrides method_missing and then Rails steals it back and you get all sorts of bugs. Sure you can do some kinda ugly stuff to steal it back again, but then the Rails source changes and you have another bug to chase. So with the release of Rspec 0.8 there is a new syntax supported and the old way has been deprecated (the maintainers of Rspec promise that they will provide a conversion script before they take the old syntax out of the framework). So



object_instance.should include(‘thing’)

Why? Because now RSpec just has to add a ‘should’ method to the Object class which takes in an operator. No method missing on Object at all, so Rails can do all the fanciness it wants.

There are two implications here. One, if you’re writing a ruby framework you should be careful about method_missing as your users may want it too. And two, if you’re thinking about adding method_missing to an object that inherits from a Rails object, beware.