Docunext


Metaprogramming with Ruby method missing and define method

May 21st, 2011

There are many reasons why I love working with Ruby, such as its consistency, its use of natural language, its clean object-oriented paradigm, but one aspect of it is that stands out above the rest: its metaprogramming capabilities.

What is "Metaprogramming"?

Metaprogramming is the practice of writing code that generates more code, and then running that code in the same process.

Seen from another angle, it is the practice of writing code that modifies or adds to itself as it runs. As such, in my humble opinion, its a heck of a lot easier to do with an scripting / interpreted language like Ruby than a compiled language like C.

Metaprogramming is also related to domain specific languages. In the case of Ruby, Rake and Sinatra are domain specific languages.

Examples of Metaprogramming

There are many different paths to and styles of metaprogramming in other languages; in Ruby, there are built-in hooks to help software engineers build software which will eventually make themselves obsolete! (Just a little humor there for you... or not?) The hooks are method_missing and define_method.

On their own, they are quite useful tools. Together, they are a powerful combo that are filled me with awe of Ruby - we'll touch on that later. First, what are they and what do they do. (Side note: "Who is your daddy, and what does he do?" - Anyone? Anyone?)

method_missing

If you've ever had a typo in a method call, you've likely encountered method_missing. It is called when a non-existent method is called, and its included as part of Ruby's Kernel class1.

The general practice is to override method_missing, and do something besides raising an exception. So let's get to an example, shall we?

class Foo
  def method_missing(m, *args, &block)
    if m.to_s.include? 'bar'
      puts "SNAFU"
    else
      super
    end
  end
end
yo = Foo.new
yo.foobar
yo.snafu

I've employed some standard best-practices with this example, so it isn't the simplest case, but fear not oh loyal reader, I shall explain what's going down.

First off, method_missing can take three arguments, and I've included them here. It requires the first one, which is the name of the missing method that is getting called. The next two are the arguments and / or the block that are getting passed to the method.

After that, there is a test to check whether the method name includes "bar". Why? This is a technique to ensure that non-existent methods that really should not exist are not handled. Why? So that bugs like typos can be identified. If you take a moment to ponder, you might agree that's its generally not a good idea to try and generate code for every potential method name in existence!

If the calling method's name does include the string "bar", we'll put "SNAFU", if not, we let the standard method_missing method handle it (and raise an exception). If I run this code, that's exactly what will happen:

?> yo = Foo.new
=> #
>> yo.foobar
SNAFU
=> nil
>> yo.snafu
NoMethodError: undefined method `snafu' for #
	from (irb):7:in `method_missing'
	from (irb):15

Yay! We're already metaprogramming. What happened? When yo called foobar, yo could not find a method with its name, so its version of method_missing created a temporary one on the fly. Cool, huh?

define_method

In my humble opinion, define_method is where things really get exciting, because when combined with method_missing, it is possible to start really building code during runtime.

The define_method method is found in the Module class. It is private class method, so it is called on its parent object, not on instances.

First let's take a look at what define_method can do. Examine these two classes:

class Foo
  def snafu
    puts "hi"
  end
end
class Bar
  define_method(:snafu) do
    puts "hi"
  end
end

In essence, they are identical. So why would we ever want to use something like define_method? Its often used with arrays, like this:

module Stuff
  MYKEYS = ['date','betterdate']
  MYHASH = {
          't1' => {'date' => '2000', 'betterdate' => '2000', 'dynamicdate' => '2000' },
          't2' => Hash.new('2011')
        }
end
class Test
  include Stuff
  MYKEYS.each do |k|
    define_method(k.to_sym) do |myid|
      MYHASH[myid][k]
    end
  end
end

In that example, we're creating methods to access values from a hash. This example is futile; hopefully it illustrates how define_method could be used on its own. Handy, right? Not really earth shattering though... what gives? Its limited by the fact that it is a private class method that creates class methods when its class is defined. In other words, it cannot be called directly during runtime making it a heck of a lot less dynamic than method_missing. Can you tell the difference? Good! Now let's talk about the magical combo.

A Match Made of Rubies: method_missing and define_method

I employ no hyperbole - this is where the magic happens.

Remember that the problem with method_missing is that it is a private class method, and can't be directly called from an instance at runtime. Is there another way??? Yes! Here comes method_missing to the rescue.

I'll jump right into an example:

  def method_missing(m, *args, &block)
    if ALLKEYS.include?(m.to_s)
      # Yes, smarter key
      self.class.send(:define_method, m) do
        MYHASH[name][m.to_s]
      end
      self.send(m)
    else
      super
    end
  end

Like the first example of method_missing, I've again employed some standard best-practices to use with method_missing, like the string inclusion check, and the alternate push to super, the rest is much different - what's going on with all the sends?

With method_missing, we can tell an instance to ask its parent class to call upon one of its class methods, specifically define_method: self.class.send(:define_method, m) do. In doing so, we can pass it the non-existent method's name, and whatever the non-existent method is passed. After that, we can actually call the method as an instance method. Sufferin' succotash! That is inconceivable!

Savvy readers might be wondering:

What's the advantage of doing it this way as opposed to simply using method_missing by itself?

There is a very good reason for using define_method inside of method_missing: performance. Let me elaborate.

Software engineering often involves patterns that slightly or substantially differ from one another, and sometimes those patterns are programmatic. As such, software engineers can write algorithms to identify and specify those patterns, saving themselves immeasurable time having to manually write code that consists of mostly repetitive logic that slightly differs here and there.

The problem with employing that method at compile time (or in the case of Ruby, upon instantiation), is that it can take a long time to iterate through all the possible pattern changes and generate code to support it - an issue exacerbated by the likelihood that not all of the pattern differences are significant and will never be used. So why not create a method to catch those patterns during runtime and define real methods to handle them if and when they next needed? I can't think of a reason, so let's do so. This practice is often referred to as lazy loading, and in my humble opinion, its a very cool way to work.

Other engineers agree - ORM tools like ActiveRecord and DataMapper use lazy loading to dynamically generate methods for object properties based upon characteristics of the data sets. If they created all those methods up front, it would take a much longer for Ruby applications to use them, and if they only used method_missing, they would not be optimizing when they could.

I'd love to hear your thoughts about Metaprogramming with Ruby, or this post. See any typos or areas of improvement? Use the comment form below to sound off.

See Also

Footnotes

Yearly Indexes: 2003 2004 2006 2007 2008 2009 2010 2011 2012 2013 2015 2019 2020 2022