Ruby Metaprogramming by Example

Posted on Thursday, Nov 19, 2015

In Ruby the term metaprogramming refers to the dynamic nature of the language, which allows you to define and redefine methods and classes at runtime. Metaprogramming is often presented as a very nebulous and dangerous concept, one that is difficult to explain and hard to wrap your head around, and thus should be avoided. I’d like to to take some time to show a few powerful uses of metaprogramming techniques in real live code.

One of the most common, and most misunderstood, aspects of ruby programming is the monkey patch. The monkey patch refers to the ability for ruby to dynamically define and override methods on existing classes and modules at runtime. For instance:

This is admittedly not a very useful monkey patch, but it demonstrates that we can reach into any class, including core classes like Array, and modify it on the fly. The monkey patch is actually just a symptom of a more general paradigm in ruby.

Open Classes

Ruby executes the body of the class definition as it would any other code. Interestingly the class keyword does not create the C class twice, which is what allows our earlier example of monkey patching to work. Take a look at the following example:

In the first case, class B does not exist, so Ruby defines the class and method1. The second time class B is referenced, the class exists, so instead of redefining class B, it opens the existing class and defines method2 there. The method1 is still accessible because it is still the same class.

When opening a class we can even reference class level instance variables.

Now its time to see some examples of metaprogramming in action. Lets take a look at the awesome_print gem. awesome_print is a gem for pretty printing Ruby objects. Awesome Print works with IRB as well as Pry, but for the time being, we will be focused on the IRB code path. For those that might now know IRB is the Ruby REPL distributed with MRI, and written in ruby.

To use awesome_print, first install the gem.

$ gem install awesome_print

Then in your ~/.irbrc

require“awesome_print”AwesomePrint.irb!

Taking a look at the project we see that awesome_print.rb has two main files, inspector and formatter, and then a number of core extensions which do various monkey patches on core ruby classes. Lets take a look at the irb! method defined on the inspector class. This function is responsible for intercepting IRB output and formatting it appropriately, in other words it is the “hook” into IRB’s output, intercepting what is outputted and replacing it with its own formatting logic.

Looking through the IRB source on line 661 we see the method output_value. If we monkey patch it like so, check what happens:

moduleIRBclassIrbdefoutput_valueputs'cow'endendend'dog'=>cow

So we can see that this is the method that prints to STDOUT. Returning back to the inspector irb! method, we see that this is not quite the same code as we had. Instead of opening up the class using the class keyword we use a class_eval.

IRB::Irb.class_evaldodefoutput_valueputs'cow'endend

The class_eval method allows us to execute code in the context of a class. This allows us to open up classes without the class keyword. We can’t have a class definition in a method body, so instead we can use class_eval, which allows us to monkey patch the output_value method dynamically. The class_eval method is very powerful, and allows us to completely redefine classes at runtime.

Lets take a look at another metaprogramming trick used in AwesomePrint. Open up lib/awesome_print/formatter.rb and take a look at the format method:

defformat(object,type=nil)core_class=cast(object,type)awesome=ifcore_class!=:selfsend(:"awesome_#{core_class}",object)# Core formatters.elseawesome_self(object,type)# Catch all that falls back to object.inspect.endawesomeend

The format method is the main entry point to format an object. After determining the class of the object it is about to format, the method dynamically calls the corresponding formatter using send.

The final trick I’d like to show is located lib/awesome_print/core_ext/kernel.rb. This file contains extensions on the core Kernel class. This example comes from the book Metaprogramming Ruby by Paolo Perrota.

Here we can see that AwesomePrint has opened up the Kernel module, and is defining two methods ai and ap. Why define them on the Kernel module? Since Kernel is included in Object, and object is in the ancestor chain of nearly every Ruby class, defining a method within Kernel is a handy way of making it available nearly everywhere.

I hope you enjoyed this little intro on Ruby metaprogramming. To gain a deeper insight into Ruby metaprogramming, I highly suggest Palo Perrotta’s book.