Cracking the Box Open with Module Factories

About the lesser-known Ruby Module Factories

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:

module Imageable
  # When including this module, this hook is called
  # by Ruby with the included module/class
  def self.included(model_class)
    model_class.has_many :images
  end

  def main_image
    images.find { |image| image.kind == 'main_image' }
  end
end

And using the module is very straightforward:

class Project < ApplicationRecord
  include Imageable
end

project = Project.new
project.main_image #=> <Image kind="main_image" imageable_type="Project">

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:

Imageable = Module.new

def Imageable.included(model_class)
  model_class.has_many :images
end

Imageable.module_eval do
  def main_image
    images.find { |image| image.kind == 'main_image' }
  end
end

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:

# Instances of Project respond to every method of Imageable!
class Project
  include Imageable
end

# Provides the same collection of methods as Imageable
module SuperImageable
  include Imageable
end

# Instances of Task respond to every method of SuperImageable,
# which in turn responds to every method of Imageable!
class Task
  include SuperImageable
end

"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:

module UselessAble
  def guess_what?
    puts "I'm useless!"
  end
end

# Instances of AssHole now respond to "guess_what?" and "soul"
class AssHole
  include UselessAble

  def soul
    puts 'I have no soul!'
  end
end

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.

asshole_1 = AssHole.new
asshole_1.guess_what? #=> outputs "I'm useless!"
asshole_1.soul #=> outputs "I have no soul!"

asshole_2 = AssHole.new
asshole_2.guess_what? #=> outputs "I'm useless!"
asshole_2.soul #=> outputs "I have no soul!"

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

UselessAble.module_eval do
  # This is written in the module's instance methods collection
  def guess_what?
    puts "I'm useless!"
  end
end

def UselessAble.who?
  puts 'I am an abstract behavior, but I do exist somehow!'
end

class AssHole
  include UselessAble
end

AssHole.new.guess_what? #=> outputs "I’m useless!"
AssHole.new.who? #=> Errors out! AssHole instances don't respond to this method.

UselessAble.who? #=> outputs "I am an abstract behavior, but I do exist somehow!"

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:

class Module < Object
  def module_eval
    # Only the language has the magic to make this work...
  end

  # Imagine there are a bunch of other instance methods here.
  # Module.new generates objects that respond to these methods.
end

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:

class DynamicModuleGenerator < Module
  def initialize(methods)
    module_eval do
      methods.each do |method|
        define_method(method) do
          puts "#{method} was defined as an instance method"
        end
      end
    end
  end
end

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

OneTwoThree = DynamicModuleGenerator.new(%i(one two three))

class WeirdCounter
  include OneTwoThree
end

weird_counter = WeirdCounter.new
weird_counter.one #=> outputs "one was defined as an instance method"
weird_counter.two #=> outputs "two was defined as an instance method"
weird_counter.three #=> outputs "three was defined as an instance method"

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:

OneTwoThree = Module.new do
  def one
    puts 'one was defined as an instance method'
  end

  def two
    puts 'two was defined as an instance method'
  end

  def three
    puts 'three was defined as an instance method'
  end
end

Or, more traditionally:

module OneTwoThree
  def one
    puts 'one was defined as an instance method'
  end

  def two
    puts 'two was defined as an instance method'
  end

  def three
    puts 'three was defined as an instance method'
  end
end

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:

class ModuleGenerator < Module
  def build_methods(methods)
    module_eval do
      methods.each do |method|
        define_method(method) do
          puts "#{method} was defined in the instance methods"
        end
      end
    end
  end
end

OneTwoThree = ModuleGenerator.new
OneTwoThree.build_methods(%i(one two three))

# Now we can include OneTwoThree somewhere

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:

class Imageable < Module
  def initialize(has_one: %i(main_image))
    has_one.each do |image_kind|
      define_method image_kind do
        images.find { |image| image.kind == 'main_image' }
      end
    end
  end

  def included(model_class)
    model_class.has_many :images
  end
end

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

class Project
  include Imageable.new(has_one: %i(main_image secondary_image))
end

project = Project.new
project.main_image #=> <Image kind="main_image" imageable_type="Project">
project.secondary_image #=> <Image kind="secondary_image" imageable_type="Project">

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?

class Imageable < Module
  def initialize(has_one: %i(main_image))
    has_one.each { |image_kind| define_image_getter(image_kind) }
  end

  private

  def included(model_class)
    model_class.has_many :images
  end

  def define_image_getter(image_kind)
    define_method image_kind do
      images.find_by(kind: image_kind)
    end
  end
end

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:

class ModuleFactory < Module
  def initialize
    define_a_method
  end

  private

  def define_a_method
    module_eval do
      def my_method
        # Do something...
      end
    end
  end
end

The dirty block approach

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

module Imageable
  def self.build(methods)
    mod = Module.new do
      methods.each do |method|
        define_method method do
          images.find { |image| image.kind == 'main_image' }
        end
      end
    end

    mod.define_singleton_method :included do |model_class|
      model_class.has_many :images
    end

    mod
  end
end

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:

module Imageable
  def self.included(model_class)
    model_class.has_many :images, polymorphic: true
    model_class.extend ClassMethods
  end

  module ClassMethods
    def has_one_image(*names)
      names.each do |image_kind|
        define_method image_kind do
          images.find { |image| image.kind == image_kind }
        end
      end
    end
  end
end

class Project
  include Imageable

  has_one_image :main_image, :secondary_image
end

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!

We want to work with you. Check out our "What We Do" section!