Cracking the Box Open with Module Factories

About the lesser-known Ruby Module Factories

Thiago Araújo Silva
The Miners
6 min readDec 19, 2016

--

Metaprogramming is one of Ruby’s most powerful, intriguing and hard-to-grasp features. Ruby has a deep kind of truth that I could not yet find in any language, similar to the chicken & egg dilemma that we find in real life: an object is generated from a class, but a class is an object itself. Once you understand the truth behind this statement, everything makes perfect sense: from the beautiful object model to the expressive lines of code we can achieve with the language.

This post discusses a very useful feature that only a few Ruby programmers know: the module factory. Before getting to that, we will dig into the object model to acknowledge how modules work under the hood, then we will walk through some possible use cases of modules and how these compare to module factories.

An example to start off

Suppose we have a Ruby on Rails application with a polymorphic Image model as a single source of truth for all images.

Initially, requirements demand some of our models to be linked to one main_image each. To solve this problem, we can define an Imageable module that knows how to extract the right record from the images table and deliver an Image instance.

A good use case for modules is to share simple glue functionality among similar objects, especially when it feels like the functionality belongs to the object itself.

Setting aside has_one relationships for a reason, this is the simplest code that solves our problem:

And using the module is very straightforward:

Now Project is set to have_many images, and we can quickly obtain the main_image of any instance as if it was a normal attribute. Don’t worry about how records arrive in the database, assume that only a getter does the work we need just fine.

When the truth is unveiled

But guess what? It turns out that module Imageable is nothing but pure syntactic sugar provided by Ruby, and what the language is doing under the hood is creating an instance of the Module class. Let’s visualize the same code with another pair of eyes:

You can think of modules as objects with a particular kind of power: they allow you to write a collection of methods that can be shared among classes and other modules. The way to share this collection is via the include statement:

"extend" does the same job as “include,” but it acts in a different context. We will skip “extend” in this post.

When you “define” a method in a module, what you are really doing is writing it to a special kind of “template,” a collection called “instance methods.” Think of it as a super power conceded by the language, something that only module instances can do.

By including the module in a class, you are just “mixing in” its instance methods into the class’ own instance methods collection:

And what about classes? A class is just a specialization of a module with factory powers; hence it’s able to do something the latter can’t do: create objects provided with these instance methods.

A module is just a stripped-down class, but it possesses the most basic functionality any class needs: the ability to hold instance methods.

But wait, isn’t a module an object? Let’s get back to the deeper truth:

Getting deeper with Module Factories

Since a module is an object, it must have been generated from a class. And what class is it? Module. Remember the Module.new above? Classical object oriented programming, huh? The definition of a Module exists somewhere under the covers, and it might as well look like this:

Ruby defines “Module” automatically for us, right when our program boots up.

However, this is not just an object model fairy tale: Actually, it’s sort of ordinary at a language level. The Module class is a factory, right? Because it’s a class, we can subclass it to create our own factories:

And we can instantiate a custom module just like any other object:

In this example, the initializer of the Module class got overwritten with a new one that defines instance methods on-the-fly in every created module! Otherwise, it would have been set in stone forever:

Or, more traditionally:

With the dynamic version, we can also create a module that responds to four, five and six on-the-fly, see?

The conventional initialize method is a key factor to building great module factories. Without it, our factory would have looked kinda strange:

The ModuleGenerator example isn’t particularly useful, so let’s revisit the Imageable concern.

Module Factories for the greater good

Our requirements for Imageable have changed, and now some of our models need other kinds of one to one image fields. A single main_image field won’t do anymore.

To solve this problem, we can "convert” Imageable from a module instance to a module factory:

Now we can have as many image fields as we want in our model:

But we can do even better! Since we are using a class to create a module, why not use private methods to organize our logic?

It’s great that we are now able to name each chunk of code, thanks to our good ’n’ old class organization tools. Now it’s easier to grow our logic without turning it into a mess.

Of course, non-dynamic methods can use module_eval instead of define_method for better readability:

The dirty block approach

I’ve seen a few projects solving the same problem roughly like this:

The “Module” class’ constructor provided by Ruby can evaluate a block with the same effect as “module_eval”.

As you can see, a “factory” singleton method is being defined within a module, and it returns another module with dynamically generated instance methods.

The problem with this code is that it tends to grow disorderly and out of bounds. Imagine two, three, four, ten additional dynamic method definitions within the same block of code. Would you be able to tell exactly what they are, or even what they do? Probably so, but it would take more time to figure it out. Comments would likely help, but they wouldn’t exempt things from being ugly.

With module factories, we can use private methods to aid in organization, whereas with the dirty block approach we simply can’t. And this is a problem.

The macro approach

The “macro” approach is a familiar style used throughout Rails and other projects: the idea is to provide the target class with singleton methods that are responsible for defining other methods within the class’ instance collection. Let’s change Imageable to use this approach:

This code is not bad per se, but there’s still a potential problem with it: suppose one of the macros needs to define more than one instance method. In that case, we would have gotten stuck with chunks of complex code we wouldn’t be able to name, and it would be harder to read it overall.

We could split the logic to other macros, but that would pollute the target class’ interface with methods it wouldn’t ever need to use! Moreover, we would still be polluting the class with singleton methods anyway (has_one_image), so it’s not an approach I would call “tidy”.

The upside of macros is that they are readable, but if you’ve seen Rails models in real life you’ve probably noticed they tend to be full of these nifty calls that read like DSLs, and it may get to a point where you don’t know which macro belongs to which module anymore.

The advantages of Module factories

Module factories are relatively unusual in the Ruby world, but I’d wish they weren’t because they solve some problems in a very elegant fashion:

  • They allow to organize on-the-fly instance methods definitions.
  • They are tidy and don’t pollute the target class with singleton methods.
  • They provide encapsulation for the factory logic.
  • They are self-contained: all instance methods can be written directly in the module.
  • They can be as readable as macros.
  • You can pass options to a plain old constructor of a plain old class. Now you know to which module a feature belongs, instead of trying to guess what’s the origin of a macro you find in a class body, amidst a mess of other macros.

Wrap up

Metaprogramming is cool, but it should be used with care. It’s a very powerful feature appropriate to aid in framework code, even though there are nice use cases regarding application logic. I even tried to illustrate a good example with Imageable.

And you should also think twice before using modules as “concerns” (as known in the Rails community). There are usually better ways to solve the same problem, like object composition, for example. Nevertheless, it’s a no-brainer for simple glue code and convenient attribute emulation.

That said, if all you have is a hammer, everything looks like a nail.

I hope you have enjoyed this post. If you have any questions, just hit me up in the comments!

--

--

Thiago Araújo Silva
The Miners

Full Stack Developer. Interested in computer science and the craft of programming directed towards practical purposes.