To start this post, let’s imagine we started having a lot of different classes in our program. Is quite possible that we 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 decide to name another different class, with different 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 it helps 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
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 all of 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 have to 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 console
$ ruby modules.rb
Hello from ThingOne class
Inside the module we have just classes, but what happens if we add methods? 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 having to include (or extend) the module in any other object (we’ll see this concept below). This is very common when we are creating service objects, for example. 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 doesnt 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 that are inside of 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. 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 able for using inside the Person’s class.
Other 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’ve the Dollar class. If we want to create an object from Dollar we use the “::” notation per each module.
Naming collisions
At the beginning of the post, we said that modules help us to avoid the naming collisions, but what does that mean? We already know how the modules and mixins works in 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” that refers to a bank account. But suddenly the customer ask for a new feature related to back-office administration system and we know that we need other class named Account for the login process. We can solved 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
On this way we avoid the naming collision, because to access each of them are quite different
When to include a module or inherit from a class?
To finalize this blog post, let's try to answer this good question. You should use inheritance only when a class is a specialization of another class. 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 special vehicle, and we should use inheritance
On the other hand, if we need to reuse a group of methods in different classes you should use modules, instead of inheritance. Generally speaking, you should try to group modules by features or roles that you can apply to different classes.
So, we’ve learned a lot on thi blog post
Thanks for reading
Daniel Morales