Do YOU know Ruby's "Chainsaw" method?
7/Oct 2010
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()
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.
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 duck
doesn’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:
- I use
method_missing()
to remove duplication. A well placedmethod_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 asfind_by_name_and_address()
. - 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 amethod_missing()
that’s even harder to read and modify, then I’ve defeatedmethod_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:
- 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)