Ruby is a programming language created by Yukihiro Matsumoto (better known as Matz) a form of compilation of everything he liked the best about his favorite languages: Perl, Smaltalk, Eiffel, Ada, and Lisp. Matz was motivated to create a new language by balancing functional with imperative programming.

One of the first reactions people have when first interacting with Ruby is to say: “Wow, this is very simple!” Matz, however, states that his goal is to make Ruby natural, not simple. Matz remarks, “Ruby is simple in appearance, but is very complex inside, just like our human body.”

On its official page, Ruby is described as a “dynamic, open-source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write.” There is much more that can be said about Ruby, even in an introductory fashion, but this initial description is, in my view, spot on.

When I use Ruby, I am not thinking about some of the mechanics of programming. Instead, I am mostly thinking about the result I seek to produce. Matz wanted Ruby code to be easily read by humans. Ruby code is meant to be very elegant and simple, which makes it my favorite language for prototyping.

# What Is Mettaprogramming?

If elegance, simplicity, and the natural aspect of its syntax are already great ingredients for prototyping, my favorite thing about Ruby is something yet more intriguing: metaprogramming!

Informally, metaprogramming is often refferred to as “writing code that writes code”. If you search online, this is the most popular definition of metaprogramming: “Code that writes code.” Well, I’m not too fond of this definition. The reason is straightforward. Consider the following C++ code:

When I run g++ example.cpp -o example --std=c++11 && ./example, a new file main.cpp will be created, which contains the following code:

When I run g++ main.cpp -o main --std=c++11 && ./main, I obtain:

a = 2
b = 3
a + b = 5
a * b = 6


This is a naive example of “code that writes code.” Ok, maybe too naive, but the idea here is to illustrate the limitations of this popular definition of metaprogramming. A code that writes code is not interesting in itself. How code writes code and what you can do with that is an entirely different story.

Paolo Perrotta wrote a wonderful book about metaprogramming in Ruby. Perrotta describes Ruby source code as “a world teeming with vibrant citizens including variables, classes, and methods.” These citizens are language constructs. Therefore a more technical (and much more meaningful) definition of metaprogramming is writing code that manipulates language constructs at runtime. This concept is so important that I will break it down for better visibility:

• What: writing code that manipulate language constructs.
• When: at runtime.

I like the second definition much better. Not every language can do that, and the way Ruby achieves this dynamic manipulation of language constructs makes it incredibly elegant and powerful.

All I can do in a single blog post is to scratch the surface of metaprogramming in Ruby. For that, I invite you to look at five, amongst other building blocks of metaprogramming in Ruby: Dynamic Dispatch, Dynamic Methods, Ghost Methods, Dynamic Proxy, and Blank Slate.

# Language Constructs

For all the examples in this post, I used Ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-darwin19].

When we create a class in Ruby, that class inherits properties and behaviors from other default classes unless we decide otherwise. These classes are called “ancestors.” They provide fundamental functionalities for any custom class in their lineage. We can check what are the ancestors of my class as follows:

We can check what each of these ancestors is by checking their associated classes:

We can also list what “fundamental functionalities” are inherited when we create a class in Ruby by executing:

You see a long list of methods inherited by the classes/modules in the BasicObject’s lineage. We can ask the list of methods for the module Kernel:

Now you see an even longer list of methods than before. We can check the sizes of these lists:

We can also see precisely what the methods that belong to Kernel but not to BasicObject:

But wait:

What is going on here? Shouldn’t Object have at least 175 methods like Kernel? Actually, no. Ruby does not support multiple inheritances. One of the methods defined in BasicObject is :superclass. Obviously, BasicObject does not have a superclass (parent class):

But Object has:

So Object inherits from BasicObject. We can even check the following:

What is PP::ObjectMixin and Kernel doing “above” Object when we look at the ancestors of MySimpleClass? Simple: as we saw before, PP::ObjectMixin and Kernel are modules, not classes, and we know that a class can only have one superclass. While we can create a class that inherits from another class (and just one), we can include as many modules we want using the principle of composition. Therefore we can check:

When I created MySimpleClass, it automatically inherited from Object, which includes the modules PP::ObjectMixin and Kernel, and therefore MySimpleClass also include these modules. The correct way to read the ancestors list is as follows: MySimpleClass inherits from Object, Object includes PP::ObjectMixin and Kernel, and Object inherits from BasicObject.

How are modules included in a class? Consider the following module:

Now we create MySimpleClass and include MyModule as follows:

And we can check what instance methods we have now available in MySimpleClass:

In ruby, the top-level object (something similar to the scope of main in C) is Object, and we know Object includes Kernel. Therefore, the methods defined in Kernel are available to Object and any of its descendants without the need to refer to Kernel explicitly! This includes methods we use without even thinking, such as puts, rand, raise, catch, throw, and all the others defined in Kernel. This is why we can execute:

In fact, when you are running IRB (interactive Ruby), a REPL (Read-Eval-Print-Loop) environment, and you type

you get main. We never define main in Ruby. This is just to indicate that the top-level object in Ruby (a language where everything is an object), is an instance of Object:

## Manipulating Language Constructs

Let’s take a look at how Ruby allows us to interact with its “vibrant citizens,” as Perrotta describes Ruby’s language constructs. When you hear that Ruby is a dynamic language, you should know that Ruby is pretty serious about that.

As an example, consider the following class:

We can now interact with Ruby’s vibrant citizens in a number of ways. First, we instantiate an object of AnotherClass:

Then we information from obj and AnotherClass such as:

The above is far from exhaustive. It is undoubtedly good to interact with the language constructs in Ruby dynamically. How we do, it is even better.

# Dynamic Dispatch

Dynamic Dispatch is a technique that allows us to treat a method name as an argument that can be passed to another method that handles its execution. When we create instance methods for any given class in Ruby, we typically call them using the dot notation. As an example, consider the code below:

Alternatively, we obtain the same result using the method :send as follows:

How dynamic is Ruby? Let’s say that the last definition of MySimpleClass was the very first we created. If in a future moment I do:

and I request the instance methods of MySimpleClass, I obtain:

And if we consider the very first definition of MySimpleClass is still accessible in memory, then we obtain:

Therefore we can modify the struct of a class at the time of the execution of a program.

To show one form of Dynamic Dispatch in action, consider the following class:

For any instance method in AnotherSimpleClass, we their argumetns:

Therefore we can manipulate these language constructs for dynamically calling these methods. Let’s say that I want to call all methods with no arguments. I can do the following:

For invoking only the methods with two arguments, I proceed as follows:

The method send will call any method in the respective class, including private methods. If you want to confine the dynamic execution of methods to public methods, you can use public_send instead.

# Dynamic Methods

We already saw that we could add methods to an existing class as if we were creating the class for the first time. But there is a shorter way to do that. Consider our existing AnotherSimpleClass. We can dynamically define a new method as follows:

When it comes to metaprogramming, the advantage of using define_method instead of def method is that we can easily pass the new method’s name as an argument in the same way we call other class’ methods, which can be done at runtime.

# Ghost Methods

What happens when we call a method in Ruby? Consider the the instance obj = AnotherSimpleClass. When we call the method :first_method_with_no_arguments, Ruby looks at obj.instance_methods trying to find that method. If it finds it, it will call it. If it does not find it, then it will try to look for an implementation of a private method in BasicObject called :method_missing:

If Ruby does not find an implementation for :method_missing (I will talk about this later), then it calls :method_undefined. Let’s see this in practice:

We can’t call method_undefined using dot notation since it is a private method, as we can see here:

But we call it using :send:

Okay, we know how Ruby calls methods and what happens when it cannot find them. But what does it mean to loo for an implementation for :method_missing?

## Method Missing

There is no such thing as a compiler to enforce method calls in Ruby. Crazy, right? Even crazier is the fact that Ruby allows you to call methods that don’t exist! Let me give you one example of how useful this can be. I will create a data set called data:

I will now create a class MyDatabase and initialize it passing data as an argument. I will also create an implementation for :method_missing so we can take advantage of the dynamically creating and calling methods in Ruby:

We can instantiate MyDatabase passing the array data as argument:

We can now do:

This is just a toy example to show what kind of features one can build by dynamically manipulating language constructs in Ruby. Perhaps the most prominent example of dynamic method execution via implementations of :method_missing is the ActiveRecord, an object-relational mapping in Rails. Here is one example.

# Dynamic Proxy

In the previous example with MyDatabase, I receive whatever is passed on via method call and try to make sense of the call using pre-defined patterns. If the conditions specified are met, a method is dynamically called, returning the associated result. A similar approach is known as Dynamic Proxy. We still use the idea of Ghost Methods, but this time we forward the call to another method (which can be in another module or class). The most significant difference between Ghost Method and Dynamic Proxy is how to deal with responsibility. When working with Ghost Method in a particular class, you have the responsibility of implementing :method_missing and deciding when and how to give up and let Ruby call :method_undefined. With Dynamic Proxy, you forward the responsibility to another method and treat each situation according to whatever rules are in place.

Here is an example: we have class Person, and we want to “monitor” any call to a method :parse, but we don’t want to implement the logic. Instead, we will forward the logic to JSON.parse. So whatever rule JSON implemented for :parse will take place only when the method :parse is called for an instance of Person.

We can now call :parse in Person:

However, when we try to parse a different string, we obtain an error:

And that decision was made by the JSON’s implementation of :parse.

If we try something different than parse and it is a method that is not present in the list of methods of Person’s ancestors, then we obtain the default behavior for undefined methods:

# Blank Slate

Now let’s assume that for some reason, I thought that it was a great idea to implement a Dynamic Proxy for any method call starting with “display” for a new class called MyNewClass. My goal is to return just the object ID. So I create MyNewClass as follows:

So I try calling the method :display_info:

Everything seems to be working nicely, but not quite. When I try calling just :display, the following happens:

This is not what I was expecting. This happens because MyNewClass’s parent class is Object, and Object implements an instance method :display. Therefore, when I call :display, Ruby looks for a method :display in the list of methods, including Object. Ruby will find Object’s implementation of :display, which just prints the bare object and returns nil. This is a simple example of a problem that can occur very frequently when using Dynamic Proxy, especially in larger projects: the name of a “Ghost Method” can match the name of an existing method that belongs to one of the object’s class ancestors.

Most of the time, we need a fully-featured object with all the methods defined in Object. Some other times, we need some simpler. A class with a minimum number of methods is referred to as Blank Slate. One way to solve our problem is to modify MyNewClass to inherit from BasicObject instead of implicitly inheriting from Object.

The class Object has 58 instance methods:

The class BasicObject has only 8 instance methods:

More importantly, BasicObject does not implement :display. So we can modify MyNewClass as follows:

MyNewClass now inherits from BasicObject, which makes MyNewClass a Blank Slate. Of course, we lose most of the functionalities we would need for a more comprehensive class, including all functionality given by the Kernel. But for the sake of this illustration, with the modification above, we can now call all variations of “display”, including :display, and we will obtain the expected result:

# Code that Writes Code

I told you before that I don’t like the “code that writes code” definition of metaprogramming, but that doesn’t mean we can’t have fun with it. Here is a simple example of creating classes and instantiating an object for these classes dynamically. Imagine that I have two files: person.csv

and product.csv

I will write a code that will read the content of person.csv, create a class Person and define its attributes based on the first line of the file and then instantiate objects of Person with the data in the remainder of the file. In fact, the code will work for any csv file following the same pattern, therefore the same will ocurr for product.csv.

Now I can run process_csv.rb, which returns the following:

# Refactoring with Metaprogramming

Now that we have seen some of the basics of metaprogramming in Ruby let’s review a very interesting example Perrotta discusses in his book (slightly modified here for simplicity). Imagine that you are analyzing a very strange legacy Ruby code full of duplications. Your task is to improve it as much as possible. You receive two files: data_source.rb and duplicated.rb. The data_source.file is partially shown below:

The exact logic of DS is suppressed in the display. Just assume that when you pass a workstation_id as an argument to one of the methods in DS, DS will connect to a database and return the required information:

And here is duplicated.rb:

You know where this is going, right? You can now identify the duplications and how we can use the strategies we previously discussed to improve this code. First, we can use Dynamic Methods and Dynamic Dispatch:

Second, we can use Ghost Methods, and a Dynamic Proxy that is also a Blank Slate:

And so, we used all four strategies for metaprogramming in Ruby that we discussed in this post. Notice the method :respond_to? in the Computer’s implementation of :method_missing. When an object calls :respond_to?, Ruby will respond if that object implements the method passed as an argument. You could ask: “But isn’t the idea of :method_missing to dynamically implement a method that does not exist?” Correct. However, we are implementing the logic of method missing in Computer and checking if an associated method exists in DS. We need that method to exist in DS to make this logic work; therefore, we first check if the method exists in DS, and if not, we call the original implementation of :method_missing. Otherwise, we will continue with our implementation.

# There is More

I briefly discussed metaprogramming strategies with Ruby in this post, such as Dynamic Dispatch, Dynamic Methods, Ghost Methods, Dynamic Proxy, and Blank Slate. Paolo Perrotta refers to these strategies as “spells.” In his book, many other spells are discussed: Around Alias, Class Extension, Class Instance Variable, Class Macro, Clean Room, Code Processor, Deferred Evaluation, Flat Scope, Hook Method, Kernel Method, Lazy Instance Method, Mimic Method, Monkey Patch, Namespace, Nil Guard, Object Extension, Open Class, Prepend Wrapper, Refinement, Refinement Wrapper, Sandbox, Scope Gate, Self Yield, Shared Scope, Singleton Method, String of Code, and Symbol to Proc. Trust me: I didn’t even scratch the surface. There is much more to metaprogramming in Ruby.

# Conclusions

Ruby is a dynamic language by design. Its syntax is concise and elegant, and its constructs are available for meaningful manipulations, which takes object-oriented programming to its full potential and makes metaprogramming in Ruby a delightful experience. For this reason, I find Ruby the best language for prototyping I know.