method_missing:
A Rubyist's Beautiful Mistress

08 October 2010

I've read a few articles lately (like this one), advocating the use for the method_missing method in Ruby.

Many people seem to have a passionate love affair with `method_missing`, but aren't very careful in how they handle their relationship. So, I'd like to address the question:

How should I use method_missing?

If you don't want to read through the reasons you should stay with your companion, `define_method`, and are just looking to rationalize your love-affair with `method_missing`, you can skip directly to when to use method_missing.

When to resist method_missing

First of all, never give in to your love affair with `method_missing` without taking a moment to realize how good you have it. You see, in your day-to-day life, you rarely need `method_missing` as bad as you think you do.

Day-to-day life: proxy methods

Case: I need to allow one class to use the methods of another class.

This is the most common use-case I've seen for `method_missing`. It's especially popular in gems and Rails plugins. The pattern goes something like this:

```ruby class A def hi puts "Hi from #{self.class}" end end class B def initialize @b = A.new end def method_missing(method_name, *args, &block) @b.send(method_name, *args, &block) end end A.new.hi #=> Hi from A B.new.hi #=> Hi from A ```

So now B has all of A's instance methods. But let's think about what happens every time we call @b.hi, your ruby runtime goes up the chain to find the method, and then at the very end, it calls method_missing right before it raises a NoMethodError.

In the example above, this isn't a big deal, as there are only 2 trivial classes to look through. But often, we're programming in the context of Rails or some other framework. And your Rails model inherits from ActiveRecord, which in-turn inherits from an ass-load of other classes, and now you have quite a stack to climb... every time that method is called!

Your companion: define_method

Now you're saying, "But Steve, I need method_missing." I say to you, don't forget you have a faithful companion in define_method.

It allows you to dynamically define a method (go figure!). The great thing about define_method is that after it runs (typically when your classes are loaded), the methods it created now exist for your class, plain and simple. No climbing up the class chain when you call those created methods.

define_method is loving and dependable, and will more than satisfy in your day-to-day life. Don't believe me? Read on...

class B
  define_method(:hi) do
    @b.hi
  end
end

"But I have lot's of methods!" you say.

"No problem!" I respond with a reassuring wink.

class B
  [:hi, :bye, :achoo, :gesundheit].each do |name|
    define_method(name) do
      @b.send(name)
    end
  end
end

But I don't want to have to name them all explicitly.

Now you're getting picky, but OK.

class A
  # ... lots of methods in here
end
class B
  A.instance_methods.each do |name|
    define_method(name) do
      @b.send(name)
    end
  end
end

But what if I need to build in new, slightly modified methods that are just based on others?

That's easy.

class A
  def hi
    puts "Hi."
  end
end

class B
  A.instance_methods.each do |name|
    define_method("what_is_#{name}") do
      if @b.respond_to?(name)
        @b.send(name)
      else
        false
      end
    end
  end
end

B.new.what_is_hi #=> "Hi."
B.new.what_is_wtf #=> false

That's not very attractive.

Yeah well, you take what you can get. If you want more readable code, check out ruby delegation library and Rails ActiveRecord delegation.

So, let's put this all together to see the real power of define_method.

Adapted from this example on ruby-doc.org:

class A
  def fred
    puts "In Fred"
  end
  def create_method(name, &block)
    self.class.send(:define_method, name, &block)
  end
  define_method(:wilma) { puts "Charge it!" }
end
class B < A
  define_method(:barney, instance_method(:fred))
end

a = B.new
a.barney                                #=> In Fred
a.wilma                                 #=> Charge it!
a.create_method(:betty) { p self.to_s }
a.betty                                 #=> B

When to use method_missing

By now you're thinking, well there's got to be a good reason to use it, or it wouldn't exist. And you're right, there is.

Dynamically named methods (aka "meta-methods")

Case: I want to provide a set of methods, based on some pattern, that do something as a function of the method name itself. I probably will never call most of the possible methods, but they need to be available.

Now you're talking! This is actually how ActiveRecord provides you with all those fancy dynamic attribute-based finders, like find_by_login_and_email(user_login, user_email).

def method_missing(method_id, *arguments, &block)
  if match = DynamicFinderMatch.match(method_id)
    attribute_names = match.attribute_names
    super unless all_attributes_exists?(attribute_names)
    if match.finder?
      # ...you get the point
    end # my OCD makes me unable to omit this
    # ...
  else
    super # this is important, I'll tell you why in a second
  end
end

It's all about proportion

method_missing is the perfect tradeoff when you have a large proportion of available meta-methods compared to the amount that will actually be used.

Think about the attribute-based finders in ActiveRecord. In order to define those methods up front with define_method, ActiveRecord would have to introspect the columns available for each model's table, and in each model build methods for every conceivable combination of columns.

find_by_email
find_by_login
find_by_name
find_by_id
find_by_email_and_login
find_by_email_and_login_and_name
find_by_email_and_name
# ...

You get the point. If your table has 10 columns, that's 10-factorial possible combinations! $latex 10! = 10 \times 9 \times 8 \times \ldots = 3,628,800$ So, you would have to define 3,628,800 methods at run-time, and then ruby would have to keep those methods in memory.

All I'm saying is, if you think you can perform with this kind of overhead, you better be Tiger Woods.

The right way to use method_missing

It's normal at this point in your life to feel attracted to method_missing. Even though you're committed to define_method, there are times when you just need to be with method_missing. If you're going to do it, please just be safe.

As long as you practice safe-programming, you can use method_missing worry-free. I'll leave you with four safe-programming guidelines to follow:

1. Get tested

Be responsible and know what your code is doing and who it's associating with. You don't want to be running code arbitrarily every time you call some method that doesn't exist. So, before you run your code inside method_missing, test the method call. Maybe something like this:

def method_missing(method_id, *arguments, &block)
  if method_id.to_s =~ /^what_is_[\w]+/
    # do your thing
  end
end

2. Wrap it up

When you're inside method_missing, after you've tested the called method and determined it does indeed match your criteria to run your special code, wrap your code in a define_method block:

def method_missing(method_id, *arguments, &block)
  if method_id.to_s =~ /^what_is_[\w]+/
    self.class.send :define_method, method_id do
      # do your thing
    end
    self.send(method_id)
  end
end

Now, the first time that method is called, it runs up the class chain looking for the method. It finally hits method_missing, passes your test, and then define_method is called and the code runs. Now every subsequent call to that specific method will be defined, and it won't have to run up the entire chain.

ActiveRecord actually does this, and it's allows a considerable performance boost. Especially with Rails, where you pick one of these finders to use in your model or controller, and it then gets called over and over again.

3. Clean up after yourself

If anything goes wrong, you need to grab your stuff and get out of there. And ideally, you don't want to leave a trace. If the called method fails your criteria, at any point, call super. This will allow the method call to continue on it's merry way up the class chain, so that it can raise the appropriate exception and leave you an exception stack trace you can work with.

def method_missing(method_id, *arguments, &block)
  if method_id.to_s =~ /^what_is_[\w]+/
    self.class.send :define_method, method_id do
      # do your thing
    end
    self.send(method_id)
  else
    super
  end
end

4. Tell the world

Wait, what? Ok, so the analogy kinda breaks down here, but at least in the programming world, you want to broadcast to your application that your class now responds to the method you made.

def method_missing(method_id, *arguments, &block)
  if method_id.to_s =~ /^what_is_[\w]+/
    self.class.send :define_method, method_id do
      # do your thing
    end
    self.send(method_id)
  else
    super
  end
end

def respond_to?(method_id, include_private = false)
  if method_id.to_s =~ /^what_is_[\w]+/
    true
  else
    super
  end
end

Now, any other gems or libraries that rely on querying your class for available methods will still work with your new meta-methods. This code is a bit redundant, so check out this writeup on TechnicalPickles, which describes how ActiveRecord extracts this redundancy out. Hat-tip to Aditya Sanghi for pointing out this good practice on the #rails-contrib IRC.

Rounding out the innuendo

The lesson: These methods play an important role in every rubyist's life. Treat define_method as your faithful companion, method_missing as your passionate but careful mistress, and respond_to? as your love-child, and you can't go wrong.

About the author:

Steve Schwartz // Owner of Alfa Jango Web-based Software, creator of RateMyStudentRental & LeadNuke, engineer, hacker, rubyist, guitarist, aspiring racecar driverist.



Comments are loading...