Mixins in Ruby
Modules can introduce instance methods (behavior) into classes through the use of mixins. With the simplicity of single inheritance and the strength of multiple inheritance-like capability, but without the ambiguity issues, mixins are Ruby’s answer to traditional multiple inheritance.
A class can have as many modules as it wants; these are called mixins. A class gains access to the methods of a module when it is blended into it. Because of this mechanism’s great flexibility, code can be kept free of constrictive class hierarchies.
Key Mechanisms for Mixing In Modules
Instance methods that are shared by several classes are defined by modules. How the module is integrated is determined by the intended scope of the techniques:
Including Modules
In a class definition, the include keyword is the main method for using a module as a mixin. As a result, the instance methods of the module are accessible as instance methods for every object in that class.
Within the included class, the include
directive makes the module’s methods, constants, and class variables accessible. If a module’s methods are modified after it has been included, the updated behavior will be reflected in the methods of all classes that used that module.
Code Example: Including Instance Methods
In this example, SomeMixin
provides the instance method foo
to the Bar
class:
module SomeMixin
def foo
puts "foo!"
end
end
class Bar
include SomeMixin # Mixes instance methods into Bar
def baz
puts "baz!"
end
end
b = Bar.new
b.baz # => "baz!"
b.foo # => "foo!" # works with the mixin
Output
baz!
foo!
Extending Objects
Specific objects (as opposed to the entire class) can have methods from a module added to them using the Object#extend
method. As a result, the receiver’s instance methods are changed to singleton methods methods specified on a single object from the module.
The methods added become class methods if extend
is applied to the class itself (since classes in Ruby are objects).
Code Example: Extending to Add Class Methods
The function foo
from SomeMixin
is added straight to the class Bar
in this example, converting it to a class method:
module SomeMixin
def foo
puts "foo!"
end
end
class Bar
extend SomeMixin # Adds foo as a class method to Bar
def baz
puts "baz!"
end
end
# Create a new instance of the Bar class
b = Bar.new
# This call works because baz is an instance method
b.baz
# This call will fail because foo is a class method, not an instance method.
# To demonstrate the error without crashing the program, we'll use a begin/rescue block.
begin
b.foo
rescue NoMethodError => e
puts "Correctly raised a NoMethodError: #{e.message}"
end
# This call works because foo is a class method, called on the class itself
Bar.foo
Output
ERROR!
baz!
Correctly raised a NoMethodError: undefined method 'foo' for an instance of Bar
foo!
Notable Mixin Modules
A lot of Ruby’s built-in features are made possible by mixins:
Enumerable: The most popular mixin module is probably this one. It specifies traversal and search methods like collect
, inject
, find_all
, and sort_by
. You can add Enumerable and get 22 methods for free if your class implements the necessary iterator function.
Comparable: This module is included by classes whose objects can be ordered. By including Comparable
and implementing the spaceship operator (<=>
), which returns -1, 0, or +1 for comparison, the class automatically gains comparison operators like <
, <=
, ==
, >
, >=
, and the between?
method.
Mixin Interaction and Behavior
Method Resolution: Ruby first looks for a method in the immediate class, then it looks for the mixins that are part of that class, and lastly it looks for the superclasses and their mixins. In the event that a class has more than one mixin, the hierarchy searches for the most recent module.
Decorator Pattern: The decorator approach is frequently implemented using mixins, which provide objects functionality without changing other objects of the same type. Adding modules such as CheesePizza or LargePizza with extend, for example, might dynamically change an object’s behavior (e.g., modifying its cost).
Initialization Hooks: The included callback function allows modules to run code while it is being included. This hook is helpful for carrying out tasks like automatically adding class methods to the class.
Instance Variables: Because instance variables defined in a mixin module are produced in the host object (self) that contains the module, they may conflict with instance variables from the host class or other mixins. Mixins that need unique internal state should utilize unique instance variable names, possibly prefixing them with the name of the module.
You can also read Instance variables In Ruby: What They Are & How To Use Them