Back-end Engineering Articles

I write and talk about backend stuff like Ruby, Ruby On Rails, Databases, Testing, Architecture / Infrastructure / System Design, Cloud, DevOps, Backgroud Jobs, and more...

Twitter:
@daniel_moralesp

2019-09-27

Modules and Mixins in Ruby OOP

To start this post, let's imagine we started having a lot of different classes in our program. As a result, we may end up with equal class names. For instance, you decide to name a class as "Figure." Months later, you'll start to forget the names given to all your classes, and you choose to name another class with erratic behaviors as "Figure". This is called: name collision. That's annoying because you have to consider all the current class names to assign a new one.

Modules

Here is where modules come in help. Modules help us to avoid collision names in Ruby and, at the same time, allow us to organize our code better. So a module is a class, method, and variables container. Modules are defined with the Ruby keyword "module". Let's create our first module in a file called module.rb

module MyModule
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
 


It is easy for us to understand what we have inside the module. We have 2 methods and 2 classes, and MyModule acts as a wrapper for these inner components. The question now is how can we access these methods and classes outside the module?

To access classes and methods defined inside the module, we must write the module's name swallowed by "::" (double colon), followed by the method or classes' name. Let do an example:

module MyModule
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
i = MyModule::ThingOne.new
i.hello = "Hello from ThingOne class"
puts i.hello


Let's execute in the console.

$ ruby modules.rb
Hello from ThingOne class


Inside the module, we have just classes, but what happens if we add methods? So let's do it and try to execute the code as follows:

module MyModule
 def self.method_one
   "hello from method 1"
 end
 
 def method_two
   "hello from method 2"
 end
 
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
m = MyModule::ThingOne.new
puts m.method_one
 


Can you see the difference between method_one and method_two? The difference is that we added "self." at the beginning of the method's name. 

If we try to execute this code, we'll see the following error:


$ ruby modules.rb
modules.rb:19:in `<main>': undefined method `method_one' for #<MyModule::ThingOne:0x000000000143ab10> (NoMethodError)
Did you mean?  method

The self.method_one is a module method, which means that we can use it without including (or extending) the module in any other object (we'll see this concept below). This is very common when we are creating service objects, for example. So we can call our module method like this:


module MyModule
 def self.method_one
   "hello from method 1"
 end
 
 def method_two
   "hello from method 2"
 end
 
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
puts MyModule.method_one


And the result is

$ ruby modules.rb
hello from method 1

This works because of "self". Later we'll explain the "self" method in detail. 

If we continue, we keep the same problem with method_two. This method doesn't have the "self" method, so it will throw an error if we try to instantiate it like this:

module MyModule
 def self.method_one
   "hello from method 1"
 end
 
 def method_two
   "hello from method 2"
 end
 
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
m = MyModule::ThingOne.new
puts m.method_two



This is because methods cannot be called directly from the module; we've to mix the module inside a class. 

Mixins

Modules can be included inside a class using the keyword "include". This will include all "MyModule" methods inside the new class. Let's see how

module MyModule
 def self.method_one
   "hello from method 1"
 end
 
 def method_two
   "hello from method 2"
 end
 
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
class Person
 include MyModule
end
 
p1 = Person.new
puts p1.method_two


Let's execute it

$ ruby modules.rb
hello from method 2

Now, the method works! Now the question is, how can we access the classes like ThinOne and ThingTwo inside the module? We'll do that inside the class Person. Let me show you how

module MyModule
 def self.method_one
   "hello from method 1"
 end
 
 def method_two
   "hello from method 2"
 end
 
 class ThingOne
   attr_accessor :hello
 end
 
 class ThingTwo
 end
end
 
class Person
 include MyModule
 
 def thing_one
   ThingOne.new #there is no needed MyModule:: prefix
 end
end
 
p1 = Person.new
t = p1.thing_one
t.hello = "Hello World!"
puts t.hello


Let's execute the code.


$ ruby modules.rb
Hello World!

And now we accessed the ThingOne class inside the module. 

At the same time, it is quite possible to mix more than one module inside a class. For example, let's suppose that we have 2 modules: Module1 and Module2; we can do the following:

class Person
 include Module1
 include Module2
end


So, every method inside Module1 and Module2 will be used inside the Person's class.

Another use case scenario is when we nest modules, for instance:

module System
 module Currency
   class Dollar
   end
 end
end
 
System::Currency::Dollar.new



In this example, we're nesting the module Currency inside System. Inside the Currency module, we have the Dollar class. If we want to create an object from Dollar, we use the "::" notation per module. 


Naming collisions

At the beginning of the post, we said that modules help us avoid naming collisions, but what does that mean? We already know how the modules and mixins work at a high level now; let's see the cases when we could have name collisions.

Let's imagine that we have the class named "Account," which refers to a bank account. But suddenly, the customer asked for a new feature related to the back-office administration system, and we know that we need another class named Account for the login process. We can solve this with the following code.

class Account
 # code form bank account
end
 
module Admin
 class Account
   # code for login process
 end
end
 
bank = Account.new
login = Admin::Account.new
 

In this way, we avoid the naming collision because accessing each of them is pretty different.


When to include a module or inherit from a class?

To finalize this blog post, let's answer this good question. You should use inheritance only when a class is a specialization of another class. For example, a Circle is a geometric Figure but a more specialized one. It's better to use inheritance for this case. A bus is a vehicle, so it's a particular vehicle, and we should use inheritance.

On the other hand, if we need to reuse a group of methods in different classes, we should use modules instead of inheritance. Generally speaking, you should group modules by features or roles that you can apply to other classes. 

So, we've learned a lot from this blog post

Thanks for reading
Daniel Morales