Ruby :: Mixin Things Up!

During my first month at Turing, we were introduced to the concept of Modules and Mixins. Modules are essentially used to hold a collection of methods and/ or constants and are defined similar to a class, except that we use the ‘module’ keyword instead of the ‘class’ keyword. We would thus define a module as follows:

module AwesomeName
def some_method
puts "This method is awesome."
end
end

We can now make this method available to a class as if it were defined in the class itself. To access the methods of a module in a class, we would need to ‘include’ the module in the class as follows:

class NotSoAwesome
include AwesomeName
attr_reader :name

def initialize(name)
@name = name
end
end

By including (or mixin’ in) our module ‘AwesomeName’ in the class above, we effectively have made the method ‘some_method’ available to our class ‘NotSoAwesome’ (even though we don’t see the method anywhere in the class itself). We could thus call ‘some_method’ as follows:

> nsa = NotSoAwesome.new("Nowitsawesome")
# => #<NotSoAwesome:0x00007fb24f162458 @name="Nowitsawesome">
> nsa.some_method
This method is awesome.
# => nil

A Module is similar to a class, but whereas a class provides both state (instance variables) and behavior (instance methods), modules only provide behavior. Moreover, a module can’t inherit from a class and one can’t create an instance of a module. In Ruby, a class can inherit features from a parent class, but cannot directly inherit features from multiple parent classes (multiple inheritance). Ruby modules pave the way to sidestep this drawback, by allowing a class to inherit functionality from multiple modules, by way of Mixins. Let’s say we have four different modules, each having two methods, as follows:

If we now ‘include’ these four modules in our ‘NotSoAwesome’ class, we can allow the class to inherit all the eight methods (and the corresponding functionality) from these modules (multiple inheritance).

Modules are great as they can be used to share common methods between multiple classes by ‘mixin’ them into the classes that need to share these methods. We could give any number of classes access to the methods in these modules.

In the example above, each of the three classes (A, B and C) has access to the eight methods, across the four modules.

Let’s look at the practical usage of Mixins with a real-world example. While working on a school project, my project partner and I, realized that a lot of classes were using the same methods. For example, here is the InvoiceRepository class:

and here is the MerchantRepository class:

Look closely and you will see that there are four methods [all, inspect, load_merchants(filepath) (in the MerchantRepository class) and find_by_id(id)] that are common to these two classes. In all, these four methods were common to 6 classes in our project. What if we were able to create one module that contained these 4 methods, and then just called those methods into these 6 classes, by including this module in each class? We thus decided to carve out these 4 methods into a separate module named ‘Repository’, as follows:

We had to ensure that there was no naming conflict with respect to the variables and methods used in the module, as we were going to use this module across 6 classes. In other words, we couldn’t use any names that were unique to any one of the classes that would use this module. We thus decided to use generic names for our variables (viz. ‘csv_items’) and for our methods (viz. ‘load_children(filepath)’).

Once the module was built, it was time to ‘include’ it into our classes. This was the easy part as all we had to do was introduce an ‘include Repository’ line into each class. The trick was to associate the variable and method names unique to the class, with the generic names in the module. For example, as shown above, the MerchantRepository class initializes as follows:

def initialize(filepath, parent = nil)    
@merchants = []
@engine = parent
load_merchants(filepath)
end

The instance variable @merchants and the method load_merchants(filepath) is unique to this class. How do we sync up our module with this MerchantRepository class, so that it references the correct instance variable and method? Let’s take a look at the amended code for this class (only relevant portion included):

By replacing the instance variable @merchants with @csv_items and the method load_merchants(filepath) with load_children(filepath), we were able to incorporate the module into our class. But the class still doesn’t know how to associate merchants with csv_items. To do so, we created a new method called merchants which references the csv_items variable. Similarly, the load_children(filepath) method needs to reference the Merchant class (which is unique to the MerchantRepository class) and so we created a second method called child that references this Merchant class. We thus end up linking the generic variables in the module with the specific variables in our class.

We can now do the same for each of the remaining 5 classes. Our InvoiceRepository class (shown earlier) now looks as follows (only relevant portion included):

By creating one module, we were able to remove 4 methods from each of the 6 classes, thereby avoiding repetition, while making our code look tighter and cleaner.

Easy Peasy!