Modules in Ruby
The one that cannot be instantiated like the Class!
Prerequisite
To better understand this topic, I would recommend that you have a solid knowledge of Classes in Ruby.
Definition
Module is a collection of constants, methods, classes, and variables in a container.
It's similar to a Class but the key difference is that modules cannot be instantiated like the Classes.
# module begins
module Test
# end of scope
end
A module is initiated by module
keyword followed by ‘module_name’, Test
with an initial letter as an uppercase, because they’re constants. And finally ending its scope by end
keyword.
Nested modules
Yes! Modules can be nested too, just as nested classes or nested methods.
We can directly access the inner module Inner
, by calling its parent module Outer
followed by a double colon operator ::
followed by name of the inner module. (Line #9)
We can also reopen the module just like we can reopen Rubys’ built-in classes or our own defined classes, and directly access the inner module, as shown in line #12. This style benefits the programmer to reduce the number of indentations from 2 lines to 1.
Constants in a Module
We access constants of a module by giving module_name, Test
, followed by double colon operator, ::
, followed by constant_name, Module_constant
.
The Module_constant
is a constant object which is available to be accessed by the methods, classes, and modules defined inside the module Test
. However, the constant is not accessible outside the scope of the module Test
.
Methods in a Module
Defining & accessing both instance and module methods is similar to how we do for the Classes.
Since we cannot instantiate a Module as a Class, accessing instance methods will be later discussed in this article in section Mixin.
Class in a Module
Just as we can access constants from the Module, we can access classes outside the Module's scope.
Why use a Module instead of a Class?
For these 2 reasons :
- Namespace
- Mixin
Let’s discuss it:
Namespace
Namespace is a way of bundling logically related objects together. It allows classes or modules with conflicting names to co-exist while avoiding collisions.
An example of this is the Rails module.
Here Application
class is defined within the scope of a Rails
Module.
As the Application
class is a pretty common class name that we could encounter in another gem, so to avoid clashing of similar names, the Application
class is encapsulated in the Rails
module.
This means that class Rails::Application
will never clash with an Application
class or Other_gem::Application
class defined anywhere else.
Mixin
Mixin is a facility in which a module can be called in another module or class by using
include
,prepend
andextend
keywords.
Include:
Here, we create a class Klass
outside the scope of the module Mod
, and we make Mod
modules’ instance methods available as an instance to the class Klass
.
When we call .ancestors
method to the Class Klass
, it gives a list of classes and modules that Class Klass
descends from.
Before we call include
, the class Klass
directly descends from Object
, but after include
call, the class Klass
now directly descends from Module Mod
.
The include
keyword adds methods of the declared module to the class it's declared in as instance methods.
Or in other words:
The include
keyword makes the module a parent of the class it's declared in an ancestor chain.
Therefore, when we call an instance method to the class Klass
, they find an instance method in their parent Module Mod
and gives output.
The module methods, however, are only available to module Mod
itself.
Now what if we include
2 different modules with the same ‘method_name’ in a single class?
The 2nd module imported directly descends from 1st module.
So when we call method_1
it will first look if it’s available in class Klass
, then it will go 1 step up the hierarchy to module Mod_2
to find the method, it finds it there and gives output.
If we remove the method_1
from Mod_1
this wouldn’t give any different output because it never goes there to find the method, however, if we remove method_1
from Mod_2
then it will find method_1
in Mod_1
and give its output.
prepend
The prepend
keyword makes the module a child of the class it's declared in an ancestor chain.
Or,
The class where prepend
keyword is declared is a parent of the declared module.
Therefore, when I give a method that is both available in module and class, it will first look in the module as its first member in the ancestor chain.
extend
The extend
keyword adds instance methods of the declared module to the class it's declared in as class methods.
Or,
The extend
keyword makes instance methods of the module an instance method to the singleton class of the class where extend
is declared in.
In other words,
The extend
keyword brings instance methods of a Module, Mod
, as a singleton method of class Klass
.
Klass.singleton_methods
# => [:method_1]
The ancestor chain of the class Klass
isn’t affected, but of the singleton class...
p Klass.singleton_class.ancestors[#<Class:Klass>, Mod, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
The module Mod
is a parent of a singleton class Class:Klass
. So when we call Klass.method_1
, we are accessing method_1
from singleton class, Class:Klass.
Access module from different file
I have explained this in detail in this article.
Additional Resources
A real-world example of prepend:
Rubys’ built-in modules are:
Any suggestions or edits in this article are always welcome! Thank you!
My previous article: A Class in Ruby
My next article: An Enumerable Module in Ruby