I used method_missing once… once.

August 13, 2007 – 10:06 am

Metaprogramming in Ruby is fun, profitable, and (at times) unbearably slow. One of the biggest time sucks is method_missing - basically,it allows you to specify code that will run whenever an undefined method is called on your object (in Rails, this is most familiar in the dynamic finders - Person.find_by_…).

The performance problem with this is explained very well at thirdbIT’s post on method_missing. Every time you call method missing, you’re guaranteeing a full search through the inheritance hierarchy to see if the called method is defined at any point.

Happily, you can minimize this issue by using another Ruby metaprogramming trick: define_method. Take a look at the following


def method_missing(method_id, *args)
method_definition = Proc.new {
# do something complicated
}
self.class.class_eval %{
def #{method_id}(*args)
#{method_proc}.call(*args)
end
}
self.send(method_id, *args)
end

This code catches any undefined methods and defines them on-the-fly - so you ask for a method once and incur the cost of running through method_missing, but on subsequent requests you’ve got the method defined. Astoundingly, in my rough benchmarks, defining methods on-the-fly through class_eval is faster than defining them in advance (yes, the Ruby VM is still suboptimal, why do you ask?) On the other hand, if you use define_method to cache the method definitions, it’s slower than going through method_missing each time. The moral of that story: make sure to benchmark anything funky you try in Ruby.

I first started thinking about this technique in the context of Rails’ dynamic finders, and I’m happy to report that it looks like the core team (in the person of Koz) may be adding something like this to ActiveRecord soon.

Post a Comment