Metaprogramming as a tool for refactoring Ruby on Rails controllers

Once you learn about metaprogramming in Ruby on rails you probably want to use it all the time. Once I learned about it, I went looking for places in my code to use it. Here is a simple example of a controller that is in need of some love. This is a controller for a blog or a news site.

A few methods have been added which get the top, latest and trending articles respectively. All methods call another method on the Article model to select articles. This example does not include any pagination and other things that you usually do in these methods.

As you can see, all methods pretty much do the same thing: Call a method on Article and set the returned articles to the @articles instance variable.  In a more realistic example the methods probably have more code in them and there might be a few more to handle other cases, such as articles in a specific category or the worst articles. I’m going to show you one way to remove these methods. There are other ways to do this, but I am going to focus on one that uses pure Ruby and a bit of metaprogramming magic.

First of all, as always, make sure you have tests for these methods before you start messing around with them. This is critical when you do any refactoring and even more so when you start using metaprogramming tricks to clean up your code.

To refactor these methods,  define_method will be used to define new methods on the ArticlesController at runtime. Create the following method in the ArticlesController class:

define_method will create a new method on the current object. Since you want the created methods to be available in all instances of ArticlesController, then define_top_method needs to be defined on the class ArticlesController. It takes an argument name and then calls define_method to create a new method on the current object. This method will use send to call the correct method on the Article model, because that method has the same name as the controller action. If this is not the case, then you can simply use another parameter in define_top_method to set the name of the method to call on Article.

You can now replace all three of the original methods above with the following:

Make sure that these three method calls are made below the definition of define_top_method because you call them directly on the class and the called method must exist before that it possible.

So far this is pretty good, but we can make this even more compact by changing define_top_method like so:

define_top_method now takes any number of arguments and will create a method for each of them. The three calls to define_top_method can now be replaced by one and the final class will look like this:

This is arguably less readable than the original code, but you have to imagine that each of the three original methods probably consist of a few more lines of code and there will probably be more of them.

If you want to learn more about define_method, or metaprogramming in Ruby in general, then I recommend the book Metaprogramming Ruby by Paolo Perrotta. It teaches you everything you need to know about metaprogramming in Ruby.

Leave a Reply

Your email address will not be published.