Ruby Pattern: Parameterized Module Inclusion

I’ve run across a pattern in Ruby lately that I really like. It solves some problems that I’ve struggled with for several years. Let me start with the problems.

Let’s say you want to include an ORM in a model class, and want to tell it what database table to use. Typically, you’d do this:

class User
  include MyORM::Model
  table 'people'
end

But that table call is more like an option to the module inclusion than anything else. So what we’d really like is something like this:

class User
  include MyORM::Model, table: 'people'
end

But that’s not valid Ruby; include doesn’t let you pass anything other than a module.

So when I was learning about Virtus, I noticed that its example of how to include it is a bit different than the standard Ruby idiomatic include:

class User
  include Virtus.model
end

At first glance, it reads like the first example. But on closer inspection and consideration, it’s quite a bit different. Where MyORM::Model is a constant that refers to a module, Virtus.model is a method call. So there’s a method named model in the Virtus module. That method returns another module — which is exactly what’s needed in order to include it into our model class.

The easiest way to implement Virtus.model would be this:

module Virtus
  def model
    ::Virtus::Model
  end
end

module Virtus::Model
  # ...
end

If Virtus.model doesn’t need to take any arguments, that’s perfectly fine. In fact, I’ve started to use this implementation of the pattern for modules that don’t need parameters.

Because Virtus.model is a method, we can also call it with options:

class User
  include Virtus.model(constructor: false, mass_assignment: false)
end

We could even pass a block. But how do we process those options? There are a few different ways. However we do it, we have to be sure to return a module. And we can create modules in a few different ways.

Virtus uses the builder pattern. It takes the parameters passed in and builds a module dynamically. By that, I mean that it calls Module.new and then adds methods to that module. It does this by mixing in other modules, but it could do it by dynamically defining methods as well.

I’ve never seen this pattern in any other language. It’s obviously only possible because we can dynamically create modules.

The use of this idiom seems to be catching on a bit in the Ruby community. I’ve started using it myself, and will be adding it to my Includable::ActiveRecord gem soon.

 

 

2 thoughts on “Ruby Pattern: Parameterized Module Inclusion”

  1. I think it would be interesting to try using a block as the config to your include. Make a capitalized method that returns a module. Then you could pass a block with curly braces to that method.

    class User
      include MyORM::Model {
        table "people"
      }
    end

  2. I seem to have forgotten to mention that this is related to the Factory pattern. In fact, I think it’s a special case of the Factory pattern.

    I thought I was using this pattern on the following code, but realized that it’s just the Factory pattern:

    module Preserves
      def self.repository(*options, &block)
        repository = Repository.new(*options)
        repository.instance_eval(&block)
        repository
      end
    end
    
    UserRepository = Preserves.repository(model: User) do
    end
    

Leave a Reply

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