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
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
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
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.
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.