Dry with Ruby metaprogramming

You’ve probably already heard of Ruby’s metaprogramming capabilities but you may either think it is only suitable for building frameworks and DSLs or you don’t know when to make use of it. Here we’ll show you how little doses of metaprogramming can help to dry up your Rails app. It’s definitely a tool to keep in your toolbelt but it generally raises the overall abstraction of your code. So, watch out!

Include mixins in your modules

Modules in Ruby, besides providing namespacing, implement the mixin facility. However, modules may depend on the object they are included in. You may want to execute funcionality of your brand new module from an ActiveRecord callback. Let’s start with the following model:

Let’s say we want to track down whenever a membership is deleted. Easy enough. We just add the following:

The MembershipDestroyLogger nicely wraps it up. Note that Membership must implement the after_destroy callback in order for the module to work. Unfortunately, this couples Membership with MembershipDestroyLogger.

Let’s say that now we also want to keep track of the oauth applications that get removed. That’d involve to implement the OauthApplicationDestroyLogger. If we keep implementing more destroy loggers we will soon end up having a bunch of modules containing the same code along with the required callbacks. It’s time to dry things up.

Included hook

According to the Ruby documentation

included: Callback invoked whenever the receiver is included in another module or class

It basically executes whatever code you specified whenever a module is included into a class.

Note the method receives an argument. That represents the class where the module has been included into. That’s all we need. We can now abstract our destroy loggers.

base is the model we include the module into, so the module can add the required after_destroy callback itself. Furthermore, as self points to model we can retrieve the model’s class name.

Voilà! we can remove all the previous destroy loggers and just drop the module in whatever module we want to keep track of. So, finally our models look like this:

Conclusions

As you can see, there is no need for digging into complex and abstract metaprogramming stuff to benefit from Ruby’s features. Little doses of metaprogramming can already improve your code and lead you to a better world.

The included hook is also the underlying implementation of ActiveSupport::Concern’s included method. With this and other techniques, ActiveSupport::Concern eases the implementation of the mixin pattern considerably.

References

If you want to expand your Ruby metaprogramming skills further, Metaprogramming Ruby 2: Program Like the Ruby Pros is a recommended reading. Specially its ActiveSupport’s Concern Module.

 

Pau Pérez Fabregat

Computer science student currently working on the master thesis. Web addict.

 

Leave a Reply

Your email address will not be published. Required fields are marked *