Defining #method_missing and #respond_to? at the same time

I was reading Eloquent Ruby yesterday morning (buy a copy if you haven’t already), and it got me thinking about one of my “favorite” Ruby gotchas: defining #method_missing without a corresponding #respond_to?. E.g.:

The resulting code violates the Principle of Least Surprise, and often interacts in unexpected ways with other libraries.

I started wondering if it would be possible to define #method_missing and #respond_to? at the same time, at least for common #method_missing idioms. After some fiddling I came up with something that worked pretty well. Here’s how it looks:

And here’s the implementation:

(Also available as a Gist)

It turned out to be a relatively straightforward bit of metaprogramming. Breaking it down, it works like this:

  1. match_method is a “macro” – a method intended to be used at the class or module level to define other methods.
  2. match_method takes a matcher (anything which responds to ===, such as a Regexp) and a block. The matcher determines if the missing method name has been matched. The block becomes the body of the method.
  3. An anonymous module is created to house the new methods. Putting the methods inside their own module makes it possible to make multiple calls to match_method without each one overwriting the last one’s work, as well as for the client class to also define its own explicit method_missing.
  4. Inside the anonymous module, a new #method_missing is defined. It uses the matcher to determine if the method being called is a match, and if so, it triggers the method_body block to be called in the context of the instance. Otherwise it passes to the next #method_missing (which will be Ruby’s default #method_missing if nothing else).
  5. A method #respond_to_missing? is also defined, which simply checks to see if the matcher matches the given method name in String form. If not it passes to the next #respond_to_missing? using super. Note that Ruby doesn’t allow use of the bare version of super (which passes the original arguments along) inside a method defined with define_method. Instead I have to explicitly passa the arguments along.

    #respond_to_missing? is the Ruby 1.9 way of hooking into respond_to?. Ordinarily it would free us from the need to invoke super at all, because #respond_to? does that before checking #respond_to_missing?. But in this case we may have multiple definitions of #respond_to_missing? defined in different match_method-generated modules all included in the same class, and the super is required to invoke all of them.

    If none of that made sense, I don’t blame you. This stuff sometimes hurts my head.

  6. Finally, the generated module with its #method_missing and #respond_to_missing? methods is included into the invoking class.

The only obvious downside of this approach is that there’s no way I can find to pass a block into the instance_exec, so even though Ruby 1.9 allows passing blocks into blocks, it’s not possible to write #match_method methods which take blocks.

In the words of Joel Hodgson: what do you think, sirs?