Some metaprogramming examples from RSpec

I’m quite the curious cat and one thing that has interested me for a while has been how RSpec and its describe and it methods work. In digging through the rspec-core gem source code (v3.1.4), specifically the example_group.rb file, I came across some syntax that I had not been exposed to:

“What’s with all this passing around of blocks? And what’s :define_method doing?” I asked. The documentation for the define_method is straightforward enough, yet I still wondered what was being accomplished in the code above. In pursuit of answers, here’s what I found out.

Metaprogramming

Metaprogramming is the writing of code that acts on other code instead of data, also commonly described as “code that writes code”. As an example, let’s reopen a class at runtime and add a method:

The block passed to the define_method method is used as the body of the method being defined, which in our case is the speak method. Note the use of send over send. Because some classes define their own send method, it’s safer to use send. As another example, let’s define a method that we can use to create more class methods:

Metaclasses

A metaclass is the class of an object that holds singleton methods, and a singleton method is a method which belongs to just one object. If we have an instance, dog, of a Dog class we can define a singleton method as follows:

When Ruby looks for a method, it first looks in the object’s metaclass. If it doesn’t find it there, then it looks in the object’s class and upwards through the inheritance chain. To access an object’s metaclass we can use the following syntax:

OK, with all this in mind let’s define a class method that can be used to create singleton methods for that class. “Wait, wait, singleton methods for a class? But aren’t singleton methods for objects?” I hear you.
In Ruby, classes are objects. If they are objects, then they can have metaclasses. Let’s see it in action:

If we go back and look at the code block where we defined class method define_singleton_method for the Cat class,
we can see now that this method, by opening up the class’ metaclass and sending it define_method, is basically just creating
more class methods. And this is exactly what’s going on in the example_group.rb file; the class methods :describe, :it, etc., are created via metaprogramming. Neat!

There’s still lots more for me to learn when it comes to metaprogramming. Here’s one blog article by Yehuda Katz that I found really helpful in understanding metaprogramming, especially the role of self.