Zend certified PHP/Magento developer

RubySource: 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 lt; ActiveRecord::Base)
            raise "#{base.name} does not have a table yet" unless base.table_exists?
        end
    end
    class Person lt; 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.