CODEX

Metaprogramming in Ruby

Adam Thorne
Jan 11 · 8 min read

The term metaprogramming can be translated into self-referential programming, which describes programming that can read, generate, analyze, and transform itself or another programming. Within Ruby, the concept and its application are often described as the language’s ability to dynamically define and redefine methods and classes at runtime, but it is not limited to this implementation. This ability to define methods and transform the program at runtime allows us to write dynamic code that avoids repetition and is often reusable. Metaprogramming is used within Ruby at every turn, and understanding the core concept of how Ruby uses it to create cleaner code is an essential part of becoming a more confident and well-rounded Ruby developer. Using some coding examples, I hope to simplify metaprogramming and shed some light on the ‘magic’ that happens when working with Ruby on Rails.

Getting and Setting: Under the Hood

Fig. 1

In figure 1, the variables name, age, and breed are passed as arguments to the method attr_accessor for each new instance of the Cat class. What this method is doing under the hood is unseen, but in practice, this method call gives class access to instance variables named after the attributes passed to it and the corresponding methods used to ‘get’ and ‘set’ those variables. In figure 2, we can see what it would look like if we were to write that code ourselves:

Fig. 2

The attr_accessor has saved us from writing out those definitions, but we are still in the dark regarding how this method was able to define them for us. We could dig deep into creating our own version of attr_accessor, and we would likely be making further use of the utility methods available to us within the Ruby modules, such as define_method. This aptly named method is used to define these methods for use on the fly. We would not be going any deeper under the hood, but we would be able to have some abstract idea of what the attr_accessor is doing, looping through each of the attributes it is passed and defining both ‘setter’ and ‘getter’ methods for them at runtime. Figure 3 displays our own version of that attr_accessor method, using even more ‘magic’ utility methods in the form of instance_variable_get and instance_variable_set. These methods allow us to get and set variables we do not know the name of, which creates some very dynamic code that could be used in any application.

Fig. 3
Fig. 3.5 -> Output from fig. 3, showing that the name and age have been set and can be retrieved.

As the output shows in figure 3.5, this function works just as well as attr_accessor. Even without diving further into the raw code that would explain what these methods are doing, we have an idea of what is happening, thanks to the names of the methods we used. Ruby is a language where a lot of these methods are named almost perfectly to describe their purpose, like define_method. They can be intuitive to work with us we can find methods that are ideal for the task at hand, just by glancing at the name. Most of these methods have been designed to utilize metaprogramming to generate lines of code behind the scenes, making Ruby code, on its face, more modular, dynamic, and DRY.

The Catch-All Bucket

Fig. 4

Instead of returning this custom error, we could define behavior within method_missing that can change the way our class responds to undefined methods. Figure 5 is taken from the official documentation for method_missing, where we are given a fantastic example of where method_missing has been customized to translate Roman numerals into integers. In this implementation, the developer does not need to call the roman_to_int method, which they have defined themselves, and instead can directly pass the undefined method as an argument to roman_to_int. We use another method, named id2name, to convert that method name into a string, as required by roman_to_int. This custom method will then translate that string, as displayed in figure 5, in the method calls and their outputs:

Fig. 5

Without the use of method_missing, calling undefined methods on our instance would bring up the NoMethodError, stating that the method we tried to call was undefined for the instance object in question. As such, method_missing has transformed the expected behavior for this class and allowed us to call undefined methods. It could be said that this functionality is too broad as it does not account for methods that might not translate well into Roman numerals, so there are limits to the implementation of these catch-all techniques. Developers need to understand and consider the negative possibilities, and at least handle them before implementing these metaprogramming methods.

Introspection in Ruby

Fig. 6

This simple action, and the many other methods that do not transform our code, still use the state of our program to resolve their action, and because of this, they fall within the umbrella of metaprogramming. We can use introspection to determine what type of class something is when calling methods like class.name, which returns the name of the class as a string. Instance_of? is a method that takes in a class name as an argument, determining if the instance object it is called on is an instance of the given class name. In figure 7, we see some further introspection regarding the Cat class and the instance Tom. This example reveals just how simple metaprogramming can be.

Fig. 7

Making use of these methods of introspection allows us to have a greater understanding of our program. We can delve into the classes to determine information regarding what methods they have available with methods like methods.inspect. We still use the inspect method but direct the inspection to reveal all methods associated with that class, returned within an array. The results from this method are not always returned in an easily readable form, as shown in figure 8, so using puts with these methods is not always recommended. To check if a class can respond to a given method, we can use respond_to? which takes in a method name as an argument, and returns a boolean based on whether that method name is defined for that class or not. Again, most methods in Ruby have incredibly intuitive names, which makes our lives easy when determining which methods to use.

Fig. 8

Introspection is an amazing concept used to check the state of our program and classes. These methods can be useful when debugging, revealing what classes, methods, and instance variables we have available to us within our program. Using introspection will help produce cleaner code through our enhanced knowledge of our program and its parts.

Concluding Thoughts

Metaprogramming is taking place all the time when developing with Ruby, and many other languages and frameworks as well. When programs treat their own programming as their data, we can create functionality that transforms itself while running. The ability to write code that is incredibly dynamic and modular allows for re-usability and allows developers to spend more time spent on further functionality. Code that has those characteristics is something that can be considered ideal in the tech industry, and it is metaprogramming that allows this ideal software and code to exist.

Thanks for reading! You’re a gem!

CodeX

Everything connected with Tech & Code

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store