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.