Ruby Mixins
Have you ever wondered what it meant to “mixin” functionality into your classes? Have you seen include
and extend
used in Ruby code and wondered what was going on? If so, then you’ve come to the right place. Mixins can offer a lot of flexibility to your code and by the end of this article you’ll know how to do it using Ruby.
What’s a Mixin?
A mixin can basically be thought of as a set of code that can be added to one or more classes to add additional capabilities without using inheritance. In Ruby, a mixin is code wrapped up in a module that a class can include or extend (more on those terms later). In fact, a single class can have many mixins.
What about Inheritance?
One area I like to use mixins is for logging. In my Rails applications I like each model to have access to a logger without using a global constant of some sort. Initially I thought about using some sort of base model class (which could inherit from ActiveRecord::Base) that would provide access to a logger. However, I didn’t really like the idea of all of my models having to extend a special class just to get a logger. Yes, they would already extend ActiveRecord::Base, so what’s the difference, right?
Well, what if I wanted to add access to a logger in other parts of my application which didn’t inherit from ActiveRecord::Base (or a special subclasss)? Ruby only supports single inheritance, so mixins seemed to be the best solution.
Include vs Extend
Modules can either be included or extended depending on whether the methods will be added as instance methods or class methods, respectively.
module Logging
def logger
@logger ||= Logger.new(opts)
end
end
class Person
include Logging
def relocate
logger.debug "Relocating person..."
# do relocation stuff
end
end
In the example above a Logging
module is defined which provides an instance to a Logger
object via the logger
method. The Person
class includes the Logging
module which will add its methods as instance methods to the Person
class. It’s worth noting that everything defined in the module is added to the class including methods (public, protected, private), attributes (e.g. attr_accessor :name
) and constants.
If instead I wanted to provide a logger via a class method I’d use the extend
method as shown below.
module Logging
def logger
@@logger ||= Logger.new(opts)
end
end
class Person
extend Logging
def relocate
Person.logger.debug "Relocating person..."
# could also access it with this
self.class.logger.debug "Relocating person..."
end
end
There are three differences shown between the last two code snippets. In the latter you can see that the “logger” method creates a class variable, @@logger
, instead of an instance variable. I did this because I expected the Logger to be extended onto other classes instead of being included. Of course, I also used the extend
method instead of include
. Finally, access to the logger
method is done through the class instead of the instance, such as Person.logger.debug
.
It’s also possible to mixin a module to a single instance at runtime as shown in the following example.
module Logger
def logger
@logger ||= Logger.new(opts)
end
end
class Person; end
p = Person.new
# p.logger -- this would throw a NoMethodError
p.extend Logger
p.logger.debug "just a test"
Included vs Extended
In some circumstances you may need to know when your module has been included or extended. One use case might be to help enforce the types of classes that can include your module. For example, I had a module that would check to see if a table existed for my models, in a Rails application. However, I wanted to make sure that my module was only included in classes that inherited from ActiveRecord::Base
because the module invoked the ActiveRecord::Base#table_exists?
method.
module VerifyTableExistance
def self.included(base)
raise "#{self.name} can only be included into classes that inherit from ActiveRecord::Base, #{base.name} does not." unless (base < ActiveRecord::Base)
raise "#{base.name} does not have a table yet" unless base.table_exists?
end
end
class Person < ActiveRecord::Base
include VerifyTableExistance
end
The VerifyTableExistance
module implements the included
class method which gets invoked whenever the module is included into another module or class. This allows me to verify that the class including the module is a kind of ActiveRecord::Base
and, in this example, verify a table exists for the model.
Another common pattern is to define a module that will mixin instance and class methods.
module AcmeModel
def self.included(base)
base.extend(ClassMethods)
end
def brand
"acme"
end
module ClassMethods
def all
# get all of the AcmeModel instances
[]
end
end
end
class Widget
include AcmeModel
end
w = Widget.new
w.brand # "acme" is the output,
Widget.all # invoke the class method that was added
In this example the AcmeModel
provides an instance method named brand
. More importantly it overrides the included
class method, provided by the Module class. The included
method is a callback that gets invoked whenever the module is included by antoher class or module. At that point we extend the class that included the AcmeModel
, the Widget
class in this example, with the ClassMethods
module.
Lastly, you should also know that your module can override the extended
class method which acts as callback when the module is extended.
Conclusion
Hopefully you’ve gotten a quick overview of what mixins are and how to use them in Ruby. If not, or if you feel like I left something out, please leave comments. This is my first article and I hope to have many more. Also, if there’s a particular area of Ruby you’d like to see discussed then leave a comment.