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 wrote the Metaprogramming Ruby book for the Prags. He lives in Northern Italy with his girlfriend and a cat. He loves Ruby.
method_missing() method is a wonderful tool for every Ruby programmer. I love it. There, I said it!
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.
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.dance() are Ghost Methods: they aren’t defined anywhere, but you can call them anyway.
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 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
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 the
method_missing() chainsaw on? Here are my simple rules of thumb:
- 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.
- On the other hand, I usually think twice about using
method_missing()for cosmetic reasons, like getting cool method names such as
- 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
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
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:
- Gem Sawyer, Modern Day Ruby Warrior
- An Introduction to Outside-in Development
- Ruby Forensics
- An introduction to eventmachine, and how to avoid callback spaghetti
- The Testing Mindset
- An Introduction to Desktop Apps with Ruby
- The Ruby movement
- Almost everything is an object (and everything is almost an object!)
- So… you’re new to Ruby!
- Incorporating Web APIs to spark computer programming exercises
- 14 Ways To Have Fun Coding Ruby
- Writing modular web applications with Rack
- How to Learn Ruby (or any programming language)