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.
Comments (2)
Amos King — 2014-05-27
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
Craig Buchek — 2014-05-31
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