Do YOU know Ruby's "Chainsaw" method?

This guest post is contributed by Paolo Perrotta, a freelance geek, currently coaching agile teams for a large phone company. He also wrotes the Metaprogramming Ruby book for the Prags. He lives in Northern Italy with his girlfriend and a cat. He loves Ruby.

The method_missing() method is a wonderful tool for every Ruby programmer. I love it. There, I said it!

method\_missing \ method_missing()

Do YOU know Ruby’s ‘Chainsaw’ method?

Some Rubyists are surprised when I declare my love for method_missing(). They do have a point. As far as tools go, method_missing() is a chainsaw: it’s powerful, but it’s also potentially dangerous.

Paolo
Perrotta In case you don’t know about method_missing(), here is how it works. Imagine that you’re calling a method on a Ruby object. For example, you call duck.sing("Quacking in the Rain"). Usually, at this point one of two things happens: if duck has a sing() method that takes one argument, then Ruby executes the method; if duck doesn’t have that method, then Ruby raises an error. But wait: there is a third possibility. If duckdoesn’t have a method named sing(), but it does have a method named method_missing(), then Ruby executes method_missing() instead. Ruby also tells to method_missing() which method you originally called, with arguments and all. Here is an example:

You can say that duck.sing() and duck.dance() are Ghost Methods: they aren’t defined anywhere, but you can call them anyway.

That’s what method_missing()does. The difficult part is deciding when and how to use it. Let’s see an example of Ghost Methods in action.

Imagine that you have an InformationDesk object with many complex methods that provide some kind of tourist information or service:

To give a break to people working at the InformationDesk, you can wrap the InformationDesk in a DoNotDisturb object:

DoNotDisturb forwards method calls to the wrapped InformationDesk, unless it’s lunchtime. During lunch breaks, DoNotDisturb responds to calls by raising an exception – unless you’re calling emergency(), that works at any hour. (If you think that two hours are too much for a lunch, then you’ve probably never lived in Italy!)

The problem with the above code is that DoNotDisturb contains a lot of look-alike methods. This is a form of duplication, and duplication sucks. If a programmer adds methods to InformationDesk, she also needs to remember to add matching methods to DoNotDisturb, most likely by copy-pasting the existing look-alike methods and then modifying them. One misstep in this procedure, and you have a brand new bug.

When I work with Java or C#, I accept this kind of code duplication as a fact of life. In Ruby, I can be more aggressive and replace all the calls with a single method_missing():

DoNotDisturb is now a Dynamic Proxy: it forwards each method call to its inner InformationDesk, and it also wraps additional logic around each call. If you add methods to InformationDesk, you needn’t worry about DoNotDisturb: it will work for the new methods without any change.

This kind of trickery is useful, but it does have a dark side. If you abuse method_missing(), you can end up with code that’s difficult to read and maintain. I’m still intimidated by some of the wildest examples of method_missing() that abound the Ruby ecosystem. Even if you take care to keep your method_missing()small and maintainable, Ghost Methods are still inherently less explicit than regular methods. You can easily discover the regular methods in a class by looking at the auto-generated documentation, but you have to look at the source code (or the author’s documentation) to spot Ghost Methods.

Also, if you’re not careful around method_missing(), sneaky bugs can creep into your code. For example, an overly tolerant method_missing() might accept a mistyped method call without flinching. Chainsaws are meant to be handled with care!

So, what kind of tree should you use themethod_missing() chainsaw on? Here are my simple rules of thumb:

  1. I use method_missing()to remove duplication. A well placed method_missing() allows me to remove duplicated code at a level that wouldn’t otherwise be possible.
  2. On the other hand, I usually think twice about using method_missing() for cosmetic reasons, like getting cool method names such as find_by_name_and_address().
  3. I always try to evaluate whether method_missing() is worth the extra reading effort. I dislike duplicated code because it’s hard to read and modify. If I replace that code with a method_missing() that’s even harder to read and modify, then I’ve defeated method_missing()’s purpose.

It’s a balancing act: with some experience you can strike the sweet spot between shortness and clarity in your Ruby code. To do that, you’ll likely use the method_missing() chainsaw to cut out unnecessary branches from your forest of methods.

There are many more neat tricks, caveats and interesting details around method_missing().┬áIn Metaprogramming Ruby, I devoted most of the Methods chapter to it (you can check the first few pages from that chapter here). You can also find a lot of information on method_missing() on the Web, including some pretty crazy bug reports. With this information, and some experience, you’ll be able to make up your own mind about the pros and cons of method_missing().

Me, I already made up my mind. Yes, I use method_missing() sparingly. Yes, I keep it as short as I can. Yeah, I triple-check it for bugs… But when the duplication begins to sting, I put on my evil grin and grab my faithful chainsaw.

Have you used method_missing() before? Why don’t you share your experiences with us? Let us know in the comments section of this post. Thanks!

Post supported by Tupalo.com: Tupalo.com is a privately-held internet startup located in Vienna, Austria. Their social yellow-pages app is developed using Ruby on Rails and also uses many of the other exciting tools like Cucumber, RSpec, Capistrano and Puppet, Ruby developers came to appreciate.

Do read these awesome Guest Posts:

comments powered by Disqus