(Note: this post I originally published at the TopTal Blog)
You often hear that metaprogramming is something that only Ruby ninjas use, and that it simply isn't for common mortals. But the truth is that metaprogramming isn't something scary at all. This blog post will serve to challenge this type of thinking and to get metaprogramming closer to the average Ruby developer so that they can also reap its benefits.
It should be noted that metaprogramming could mean a lot and it can often be very misused and go to the extreme when it comes to usage so I will try to throw in some real world examples that everyone could use in everyday programming.
METAPROGRAMMING
Metaprogramming is a technique by which you can write code that writes code by itself dynamically at runtime. This means you can define methods and classes during runtime. Crazy, right? In a nutshell, using metaprogramming you can reopen and modify classes, catch methods that don’t exist and create them on the fly, create code that is DRY by avoiding repetitions, and more.
THE BASICS
Before we dive into serious metaprogramming we must explore the basics. And the best way to do that is by example. Let’s start with one and understand Ruby metaprogramming step-by-step. You can probably guess what this code is doing:
class Developer def self.backend "I am backend developer" end def frontend "I am frontend developer" end end
We have defined a class with two methods. The first method in this class is a class method and the second one is an instance method. This is basic stuff in Ruby, but there is much more happening behind this code which we need to understand before we proceed further. It is worth pointing out that the class Developer itself is actually an object. In Ruby everything is an object, including classes. Since Developer is an instance, it is an instance of class Class. Here is how the Ruby object model looks like:
p Developer.class # Class p Class.superclass # Module p Module.superclass # Object p Object.superclass # BasicObject
One important thing to understand here is the meaning of self. The frontend method is a regular method that is available on instances of class Developer, but why is backend method a class method? Every piece of code executed in Ruby is executed against a particular self. When the Ruby interpreter executes any code it always keeps track of the value self for any given line. self is always referring to some object but that object can change based on the code executed. For example, inside a class definition, the self refers to the class itself which is an instance of class Class.
class Developer p self end # Developer
Inside instance methods, self refers to an instance of the class.
class Developer def frontend self end end p Developer.new.frontend # #<Developer:0x2c8a148>
Inside class methods, self refers to the class itself in a way (which will be discussed in more detail later in this article):
class Developer def self.backend self end end p Developer.backend # Developer
This is fine, but what is a class method after all? Before answering that question we need to mention the existence of something called metaclass, also known as singleton class and eigenclass. Class method frontend that we defined earlier is nothing but an instance method defined in the metaclass for the object Developer! A metaclass is essentially a class that Ruby creates and inserts into the inheritance hierarchy to hold class methods, thus not interfering with instances that are created from the class.
METACLASSES
Every object in Ruby has its own metaclass. It is somehow invisible to a developer, but it is there and you can use it very easily. Since our class Developer is essentially an object, it has its own metaclass. As an example let’s create an object of a class String and manipulate its metaclass:
example = "I'm a string object" def example.something self.upcase end p example.something # I'M A STRING OBJECT
What we did here is we added a singleton method something to an object. The difference between class methods and singleton methods is that class methods are available to all instances of a class object while singleton methods are available only to that single instance. Class methods are widely used while singleton methods not so much, but both types of methods are added to a metaclass of that object.
The previous example could be re-written like this:
example = "I'm a string object" class << example def example.something self.upcase end end
The syntax is different but it effectively does the same thing. Now let’s go back to the previous example where we created Developer class and explore some other syntaxes to define a class method:
class Developer def self.backend "I am backend developer" end end
This is a basic definition that almost everybody uses.
def Developer.backend "I am backend developer" end
This is the same thing, we are defining the backend class method for Developer. We didn’t use self but defining a method like this effectively makes it a class method.
class Developer class << self def backend "I am backend developer" end end end
Again, we are defining a class method, but using syntax similar to one we used to define a singleton method for a String object. You may notice that we used self here which refers to a Developer object itself. First we opened Developer class, making self equal to the Developer class. Next, we do class << self, making self equal to Developer’s metaclass. Then we define a method backend on Developer’s metaclass.
class << Developer def backend "I am backend developer" end end
By defining a block like this, we are setting self to Developer’s metaclass for the duration of the block. As a result, the backend method is added to Developer’s metaclass, rather than the class itself.
Let’s see how this metaclass behaves in the inheritance tree:
As you saw in previous examples, there’s no real proof that metaclass even exists. But we can use a little hack that can show us the existence of this invisible class:
class Object def metaclass_example class << self self end end end
If we define an instance method in Object class (yes, we can reopen any class anytime, that’s yet another beauty of metaprogramming), we will have a self referring to the Object object inside it. We can then use class << self syntax to change the current self to point to the metaclass of the current object. Since the current object is Object class itself this would be the instance’s metaclass. The method returns self which is at this point a metaclass itself. So by calling this instance method on any object we can get a metaclass of that object. Let’s define our Developer class again and start exploring a little:
class Developer def frontend p "inside instance method, self is: " + self.to_s end class << self def backend p "inside class method, self is: " + self.to_s end end end developer = Developer.new developer.frontend # "inside instance method, self is: #<Developer:0x2ced3b8>" Developer.backend # "inside class method, self is: Developer" p "inside metaclass, self is: " + developer.metaclass_example.to_s # "inside metaclass, self is: #<Class:#<Developer:0x2ced3b8>>"
And for the crescendo, let’s see the proof that frontend is an instance method of a class and backend is an instance method of a metaclass:
p developer.class.instance_methods false # [:frontend] p developer.class.metaclass_example.instance_methods false # [:backend]
Although, to get the metaclass you don’t need to actually reopen Object and add this hack. You can use singleton_class that Ruby provides. It is the same as metaclass_example we added but with this hack you can actually see how Ruby works under the hood:
p developer.class.singleton_class.instance_methods false # [:backend]
DEFINING METHODS USING CLASS_EVAL AND INSTANCE_EVAL
There’s one more way to create a class method, and that is by using instance_eval:
class Developer end Developer.instance_eval do p "instance_eval - self is: " + self.to_s def backend p "inside a method self is: " + self.to_s end end # "instance_eval - self is: Developer" Developer.backend # "inside a method self is: Developer"
This piece of code Ruby interpreter evaluates in the context of an instance, which is in this case a Developer object. And when you are defining a method on an object you are creating either a class method or a singleton method. In this case it is a class method - to be exact, class methods are singleton methods but singleton methods of a class, while the others are singleton methods of an object.
On the other hand, class_eval evaluates the code in the context of a class instead of an instance. It practically reopens the class. Here is how class_eval can be used to create an instance method:
Developer.class_eval do p "class_eval - self is: " + self.to_s def frontend p "inside a method self is: " + self.to_s end end # "class_eval - self is: Developer" p developer = Developer.new # #<Developer:0x2c5d640> developer.frontend # "inside a method self is: #<Developer:0x2c5d640>"
To summarize, when you call class_eval method, you change self to refer to the original class and when you call instance_eval, self changes to refer to original class’ metaclass.
DEFINING MISSING METHODS ON THE FLY
One more piece of metaprogramming puzzle is method_missing. When you call a method on an object, Ruby first goes into the class and browses its instance methods. If it doesn’t find the method there, it continues search up the ancestors chain. If Ruby still doesn’t find the method, it calls another method named method_missing which is an instance method of Kernel that every object inherits. Since we are sure that Ruby is going to call this method eventually for missing methods, we can use this to implement some tricks.
define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method, you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method. What’s the difference between using def to create a method and define_method? There’s not much difference except you can use define_method in combination with method_missing to write DRY code. To be exact, you can use define_method instead of def to manipulate scopes when defining a class, but that’s a whole other story. Let’s take a look at a simple example:
class Developer define_method :frontend do |*my_arg| my_arg.inject(1, :*) end class << self def create_backend singleton_class.send(:define_method, "backend") do "Born from the ashes!" end end end end developer = Developer.new p developer.frontend(2, 5, 10) # => 100 p Developer.backend # undefined method 'backend' for Developer:Class (NoMethodError) Developer.create_backend p Developer.backend # "Born from the ashes!"
This shows how define_method was used to create an instance method without using a def. However, there’s much more we can do with them. Let’s take a look at this code snippet:
class Developer def coding_frontend p "writing frontend" end def coding_backend p "writing backend" end end developer = Developer.new developer.coding_frontend # "writing frontend" developer.coding_backend # "writing backend"
This code isn't DRY, but using define_method we can make it DRY:
class Developer ["frontend", "backend"].each do |method| define_method "coding_#{method}" do p "writing " + method.to_s end end end developer = Developer.new developer.coding_frontend # "writing frontend" developer.coding_backend # "writing backend"
That’s much better, but still not perfect. Why? If we want to add a new method coding_debug for example, we need to put this "debug" into the array. But using method_missing we can fix this:
class Developer def method_missing method, *args, &block return super method, *args, &block unless method.to_s =~ /^coding_\w+/ self.class.send(:define_method, method) do p "writing " + method.to_s.gsub(/^coding_/, '').to_s end self.send method, *args, &block end end developer = Developer.new developer.coding_frontend developer.coding_backend developer.coding_debug
This piece of code is a little complicated so let’s break it down. Calling a method that doesn’t exist will fire up method_missing. Here, we want to create a new method only when the method name starts with "coding_". Otherwise we just call super to do the work of reporting a method that is actually missing. And we are simply using define_method to create that new method. That’s it! With this piece of code we can create literally thousands of new methods starting with "coding_", and that fact is what makes our code DRY. Since define_method happens to be private to Module, we need to use send to invoke it.
WRAPPING UP
This is just the tip of the iceberg. To become a Ruby Jedi, this is the starting point. After you master these building blocks of metaprogramming and truly understand its essence, you can proceed to something more complex, for example create your own Domain-specific Language (DSL). DSL is a topic in itself but these basic concepts are a prerequisite to understanding advanced topics. Some of the most used gems in Rails were built in this way and you probably used its DSL without even knowing it, such as RSpec and ActiveRecord.
Hopefully this article can get you one step closer to understanding metaprogramming and maybe even building your own DSL, which you can use to code more efficiently.