Jul 08, 2015
Table of contents:
Over the last couple of weeks we’ve looked at a couple of important concepts when working with Classes in Ruby such as Inheritance and using Modules.
Ruby is a programming language that only allows single inheritance. This means a class can only inherit from one parent class.
However, there are a lot of situations where it would be advantageous to “inherit” functionality from multiple places.
Fortunately Ruby provides this functionality as Mixins. In today’s tutorial we are going to be looking at using Mixins, why you would use them and when you should use them over classical inheritance.
A Mixin is basically just a Module that is included into a Class. When you “mixin” a Module into a Class, the Class will have access to the methods of the Module.
If you are familiar with PHP, a Mixin is essentially the same as a Trait.
A couple of weeks ago we looked at the difference between Class Methods and Instance Methods.
The methods of a Module that are mixed into a class can either be Class Methods or Instance methods depending on how you add the Mixin to the Class.
To add Module methods as instance methods on a Class you should include the Mixin as part of the Class.
For example, imagine you had this incredibly useful Module:
module Greetings
def hello
puts 'Hello!'
end
def bonjour
puts 'Bonjour!'
end
def hola
puts 'Hola!'
end
end
To add these methods as instance methods on a class, you would simply do this:
class User
include Greetings
end
Now you will have access to the methods on any instance of that Class:
philip = User.new
philip.hola
# => Hola!
But if you try to call the methods as Class Methods, you will get an error:
User.hola
# => undefined method 'hola' for User:Class (NoMethodError)
To add Module methods as Class Methods, instead of using include
you would use extend
:
class User
extend Greetings
end
Now you can call the methods on the Class:
User.hola!
# => Hola!
But not on an instance of the Class:
philip = User.new
philip.hola
# => undefined method 'hola' for #<User:0x007fbd5b9ae438> (NoMethodError)
When you create a new instance of a Class, the initialize
method will automatically be invoked.
When you include a Module in a Class, the included
method will be invoked on the Module.
This makes it very easy to add both Instance Methods and Class Methods using a pattern you will see a lot in Ruby code.
For example, imagine we have the following Utilities
module:
module Utilities
def method_one
puts 'Hello from an instance method'
end
module ClassMethods
def method_two
puts 'Hello from a class method'
end
end
end
In this example I’ve separated the Class Methods into their own nested module. You don’t have to name the module ClassMethods
, but this is the convention you will see being used.
Next we can implement the self.included
hook that will be automatically called whenever this module is included in a Class:
def self.included(base)
base.extend(ClassMethods)
end
This method will receive the instance of the Class that is being instantiated. Inside of the included
method we use the extend
method to add the ClassMethods
module.
Now when we include this module in a Class we have access to both the Instance Methods and the Class Methods:
class User
include Utilities
end
User.new.method_one
# => Hello from an instance method
User.method_two
# => Hello from a class method
So hopefully it’s clear as to how to use a Mixin, but an important question is why you would want to use a Mixin?
Mixins are perfect when you want to share functionality between different classes. Instead of repeating the same code over and over again, you can simple group the common functionality into a Module and then include it into each Class that requires it.
Another important thing to understand is, when do you use a Mixin over normal Inheritance?
As I mentioned in Understanding Inheritance in Ruby, Inheritance has a lot of semantic meaning within the hierarchy of an application. There is a good reason why you can only inherit from a single parent.
Inheritance means that a class is a “type of something” and suggests specialisation. For example, a Pikachu
object is a type of Pokemon
and so it makes sense to inherit from the Pokemon
class.
When a class should be capable of something, you should use a Mixin. For example, DVD
, MP3
, and Bluray
classes all have the play
method, but just because they are all capable of the same action, does not mean they all should inherit from the same parent.
The difference is subtle, but extremely important. If it’s not immediately clear, don’t worry! With experience you will begin to understand the difference and why it is so important.
Modules are an important part of the Ruby programming language and they are something that you will see being used extensively in almost every non-trivial example of Ruby code you will see.
The concept and implementation of Modules is really very easy once you understand their purpose and what benefits they introduce.
The difference between using a Mixin and using Inheritance is slightly more tricky, but it’s one of those Computer Science concepts that is universally applicable.