arrow_backward Back to blog

Imbue: A Module Configuration Pattern for Ruby

It’s a very common practice in Ruby to use Module mixins to enhance the functionality of a class. In fact, one of the most powerful and useful features of the Ruby language is that it is so easy to do so. Great stuff all around.

Another common pattern, however, is to want to provide some include-time configuration when the module is mixed in. Let’s imagine I’m writing an extension for ActiveRecord that creates a slug based on some field. What I want in the end might be something that looks like this:

class MyModel < ActiveRecord::Base   slug :name end 

OK, that seems easy enough. How can I implement that? Well, there are a number of different ways. I could just re-open ActiveRecord::Base:

module ActiveRecord   class Base     def self.slug(field)       # do some things here...     end   end end 

Ruby gives us a lot of rope to implement things in different ways. In this case, it’s given us enough to hang ourselves. Opening existing classes like this is a bit dangerous, escpecially in an environment like Rails that has so much lazy loading of classes. How can we improve on this? Well, we could make our slug extension into a module:

module Sluggable   def slug(field)     include Sluggable::ActivatedInstanceMethods     # do something with field   end    module ActivatedInstanceMethods     # some code here...   end end  ActiveRecord::Base.extend Sluggable 

This is a little bit better: we are encapsulating the behavior of Sluggable into its own entity rather than polluting an existing class. This still doesn’t seem quite right, though: why does every ActiveRecord class need to know about this slug method? Ideally I would only modify the behavior of classes that I actually want to implement the slug behavior, not all ActiveRecord classes.

So what’s next? Well, we can do something like this instead:

class MyModel < ActiveRecord::Base   extend Sluggable    slug :field_name end 

This works fine, but it’s a little distasteful looking. Why do I need to have two lines of code in my model? Doesn’t that defeat some of the purpose of trying to abstract functionality out into its own little world?

Introducing Imbue

The more I thought about this problem, the more I felt like there needed to be a more primitive implementation of this pattern within Ruby. I want to be able to include a module into a class with the knowledge that the module is going to, in some ways, reconfigure my class with optional arguments that I pass. Of course, some would argue that if we allowed the include keyword to do this we would be stripping modules of their dignity. So can we come up with perhaps a different keyword that represents the pattern of “transformative mixin”? I propose imbue. Imbue means “Inspire or permeate with a feeling or quality” which sounds pretty similar to what we’re trying to accomplish here.

So how can we go about implementing this concept of “imbue”? Here’s a dead-simple version of it:

class Module   def imbue(mod, *args)     result = include(mod)     mod.imbued(self, *args) if mod.respond_to?(:imbued)     result   end end 

Well, that’s pretty concise, huh? Surely 7 lines of code can’t have that much effect on our problem area, can they? Here’s what these 7 lines of code let us do:

module Sluggable   def self.imbued(base, source, options = {})     base.extend ClassMethods     base.send :include, InstanceMethods     # additional transformation here   end    module ClassMethods     # relevant class methods   end    module InstanceMethods     # activated instance methods   end end  class MyModel < ActiveRecord::Base   imbue Sluggable, :field_name end 

In the end, I really like the way this looks. It’s less cryptic than the hide-the-ball class method because it gives us the fully qualified module. It’s more concise than the include-and-class-method strategy because it lets us pass options along with the module we want to include. It’s more Rubyish than the factory pattern, giving us a simple standard method call instead of using metaprogramming to generate an anonymous module. To sum up, I really like it!

This pattern is so commonly used in Ruby that I would almost argue that something akin to imbue should be a part of the language itself. It gives you a great way to encapsulate transformative behaviors that can then be applied generically. What do you think about imbue? Would you like to see it as a gem or even part of Ruby itself? Let me know in the comments.

arrow_backBack

New Project Request