Writing code that writes code in Ruby
Ruby is a very powerful language and with its metaprogramming methods, it gets even more powerful.
Ruby contains several methods that allow developers to write code that writes code. We call this metaprogramming. It sounds mindbendingly complex but hopefully I’ll distill it to its essence.
Don’t Repeat Yourself
Ruby puts a strong emphasis on the principle of ‘Don’t Repeat Yourself’, often known as the ‘DRY Principle’ . Developers tell each other to “DRY up their code” or “this code needs to be DRYer” if there are chunks of code that have been repeated or are very similar. This is so that each piece of logic is defined only once and so making a change to logic only requires a change in one, predictable place. It’s a solid, well-tested and sensible principle that any software engineer worth their salt will live by. Therefore, any language feature that can facilitate DRYer code is a good thing. Metaprogramming, amongst other things, makes sticking to the DRY principle much simpler. Let’s look at some code…
Here we define a HierarchyProcessor class in Ruby. What it actually does is not actually important but as you can see, it takes 2 inputs: a JSON string and a User object and it has 4 methods that do almost the same thing.
Using some metaprogramming techniques, we can squash this 27 line class down to 18 with very little effort.
First of all, we need to find the similarities and differences between the similar methods. What is the same and what is different? This helps us write our new meta-methods.
If we look at the first two methods, you can see I have highlighted the section of the line that is the same within all four methods. From a 58 character line of code, 43 are identical. 74% of the line of each of these methods is identical. The DRY principle says we should rectify this. If large chunks of code in a class are identical, we should condense it.
define_method to the rescue
The ‘define_method‘ method in Ruby is the secret sauce we need to fix this problem. It is a method that allows us to define methods on a class without the standard ‘def‘ syntax.
In the example above, both chunks of code define the ‘output_100’ method that just outputs ‘100’ to the console.
For the experienced Rubyist, it’s a short hop from here to defining two methods that define output_100 and output_200.
We can wrap our define_method block in a .each block and define several methods from an Array. Cool.
Let’s put this to the test and rewrite our original code using this technique:
As you can see, much shorter.
It’s not all sunshine and metaprogramming
In this contrived example you could, quite rightly, argue that the use of metaprogramming here decreases readability in favour of conciseness. This is a legitimate concern and the decision to refactor should be taken in balance. Is the level of conciseness gained worth a dip in code readability? The answer to this will depend greatly on the code you’re working with!
Metaprogramming is a huge topic in Ruby and there’s much more to say, I’ll cover another technique in a future blog post.