Hitchhiker’s Guide to Metaprogramming: Class/Module HooksBy Jonathan Jackson
Rule one to metaprogramming: Don’t Panic!
Like many others, I have struggled with the term metaprogramming. For the purposes of this article I’ll be going broad with my working definition of metaprogramming to include:
Any code that significantly raises the level of abstraction and/or any code that creates code. -Me
This definition is wildly oversimplified. But in order to start learning to metaprogram you have to have something you can point to and say “That is metaprogramming.” I’m also intentionally side-stepping the “There is no metaprogramming, there is only programming” stance here. While I partially agree, this is not the place for such a philosophical debate. If you are interested in that debate, there is a great Ruby Rogues podcast about it where several prominent “Rubyists” discuss the definition of metaprogramming in much greater detail.
To start with, metaprogramming is not magical. It will not solve all of your problems. Its usage will not make you a rock star. I’m also going to bypass the whole, “You really shouldn’t run with scissors,” warning and assume that you’ll apply sound logic in your application of metaprogramming. Caveats aside, it can be a wonderful addition to your toolbelt. Quite simply, it is a way to get your code to do a little bit more for you. If used properly, it will result in less repetitive, more easily tested, and cleaner code. It may be a little confusing to understand at first, but once you get the knack of it you can solve a host of problems that would be incredibly difficult otherwise. In Part One of the guide, I’m going to run through several class/module hooks. These hooks will allow you to improve the readability of your code. Sometimes it feels like magic, but it’s not.
If you get lost anywhere, just remember rule #1. Also, look to the end of the post for links to more resources/help.
The first hook I’d like to discuss is
#inherited. It is the hook that allows code like this to exist in Rails:
The new developer sees this and either ignores it or infers a comment above
class Posts that says something to the effect of
#there be dragons here. Not true. The “magic” is in relying on the Ruby hook provided by
#inherited. You won’t find
#inherited on ruby-doc.org because it is a private method (which ruby-doc.org does not list). Instead of calling this method you simply define a method and name it ‘inherited.’ You can see more detail at apidoc.com (this is also true of some of the other methods listed below). If you head over there, you’ll see this short description:
Callback invoked whenever a subclass of the current class is created.
Warning: drastic oversimplification inbound!
Rails does some pretty slick stuff with regards to building the
has_many method. But in the end ActiveRecord::Base defines
#inherited, which includes has_many and other modules, like so:
When you inherit from ActiveRecord::Base the above method is fired.
#initialize_generated_modules sets up the methods/modules and throws them into your Model (allowing you to build your association). That is potentially a little confusing. In the end this is what is happening:
Pretty dern cool, imho. It is important to note that
#inherited is called before subclass is fully defined, so any conditional logic that involves the subclass will not work. Also, calling
super after is reccomended after executing your custom code.
Callback invoked whenever the receiver is included in another module or class.
Every Ruby developer is familiar with
#include, which allows you to inject a module’s instance methods into a class. Similarly, to include a module’s class methods you’d use
#extend. So, if you had a module that has both instance and class methods, you’d have to do something like the following to get the entire module into your class:
Which brings us to our next hook, which allows us to execute arbitrary code whenever a module is included into a class. It’s defined on Module, and there are some idioms associated with its use. To accomplish the preceding example, you could simply define
This is just one use of
#included. Any code could be executed in the included hook, but this is a common idiom that you will see. When we include
MyModule into the
Blah class, we saw the hook fire, which calls extend on ‘base’. In this case, the argument ‘base’ represents the current
self which ends up being
Blah at the time included is called (yikes, we’ll talk more about
self in another post). Pretty Batman, right?.
I won’t go into this at length due to its similarity to
#included. It will fire arbitrary code when the module is extended. It is my opinion that doing the inverse of the above (included) idiom is less acceptable. However, I think this is still an amazing tool. A completely contrived example could look like this:
If you execute the snippet above, you’ll see that
#extended gets called when Blah extends it. Pretty neat. Movin’ on.
Extends the specified object by adding this module’s constants and methods (which are added as singleton methods).
When you call Object#extend the extend_object, callback is fired. This is especially useful when you would like to extend an object, but only if it has certain characteristics.
This will actually extend the variable
my_extended_string, which happens to be the string “blah”, with MyModule.
This is very similar to method_missing except it is called when a constant is missing or undefined. The example the docs give is pretty hairy, so I’ll make my own very contrived example of what you might use this for.
These hooks are often used in some of the more clever pieces of Ruby code floating around. Utilizing them can help you write better code. Not only that, but using these methods will also help you solidify your understanding of how Ruby executes/loads/manages each program you write.
In a talk given back in 2007, @pragdave explained some of the reasons why extending Ruby through metaprogramming is one of the mainstays of the language. He spoke of how metaprogramming in Ruby raises the level of abstraction. Essentially, this means increasing the distance between machine/language and your code. This is good for a few reasons. For one thing, the code ends up looking much more readable to both coders and clients. Also, once the underlying code is laid, a new developer has to understand less to be able to contribute. This raised level of abstraction is an amazing part of Ruby, and is prominently featured in some of its largest projects (especially Rails). I hope you find yourself exploring the hooks mentioned above in your own code. I’m sure that if you do, you’ll begin to think of Ruby a little more fondly. Thanks for reading.
This is first edition of Hitchhiker’s Guide to Metaprogramming. The next edition will cover more of the fundamentals of metaprogramming in Ruby. Learn more about metaprogramming at my blog where I’ve posted several links I’ve found helpful while writing this post. ^_^