Modules are a very powerful feature of Ruby. Primarily, you use modules for two reasons: namespacing your code and sharing common methods across different classes (as mixins).
module Taggable
# this module holds all the code
# related to tagging items.
end
# module as namespace
module Blog
class Post
# add tagging functionality to Post class
include Taggable # module as mix-in
end
class Comment
# add tagging functionality to Comment class
include Taggable # module as mix-in
end
end
Using modules, you can create classes (or modules) with the same name without interfering with other classes. This allows you to design modular programs, breaking large components into smaller ones and also mixing and matching object behaviors.
Two Ways to Nest Modules in Ruby
You can nest multiple modules to create nested namespaces. There are two ways to define nested modules (or classes) in Ruby.
- Define the second module inside the first one. If module
Rails
doesn't exist, Ruby will create it first, then define moduleActiveJob
. If moduleRails
already exists, then it will re-open it and define moduleActiveJob
inside it.
module Rails
module ActiveJob
end
end
- Use the
::
operator to define the second module on the same line.
module Rails::ActiveJob
end
There are three ways in which the second version (Rails::ActiveJob
) differs from the first:
- It assumes that the outer module, i.e.
Rails
is already defined. If it's not, Ruby will throw aNameError: uninitialized constant Rails
error. So use this syntax only if you're sure that moduleRails
exists. - It allows you to reduce the amount of indentation. Instead of 3 levels of indentation, only one is necessary.
- You can not access the constants defined inside module
Rails
inside moduleActiveJob
without the scope resolution operator. This is a significant difference; the next section covers it in detail.
Accessing Constants in Nested Modules
The scope of constant access is different depending on how the nested module is defined. Let's consider the first version.
module Rails
VERSION = "7.1.2"
module ActiveJob
puts VERSION
end
end
# output
7.1.2
You can define both modules at two separate locations, and it should still work.
module Rails
VERSION = "7.1.2"
end
# somewhere else, after the above module is defined
module Rails
module ActiveJob
puts VERSION
end
end
# output
7.1.2
Let's try accessing the constant in a module defined using the second syntax. It throws a NameError
. The reason: without any prefix, Ruby tries to access VERSION
inside Rails::ActiveJob
module, instead of the Rails
module.
module Rails
VERSION = "7.1.2"
end
module Rails::ActiveJob
puts VERSION
end
# output
uninitialized constant Rails::ActiveJob::Version (NameError)
However, you can access it using the scope resolution operator (::
) as follows:
module Rails
VERSION = "7.1.2"
end
module Rails::ActiveJob
puts Rails::VERSION
end
# output
7.1.2
Trying to access a constant defined in the parent module without ::
operator fails with the second syntax.
The following example explains this point using classes and methods.
module Rails
VERSION = "7.1.2"
module ActiveJob
def version
VERSION
end
end
end
class Application
include Rails::ActiveJob
end
puts Application.new.version # 7.1.2
However, if you use ::
to define Rails::ActiveJob
without nesting it inside Rails
, a NameError
will be raised.
module Rails
VERSION = "7.1.2"
end
module Rails::ActiveJob
def version
VERSION
end
end
class Application
include Rails::ActiveJob
end
Application.new.version # uninitialized constant Rails::ActiveJob::VERSION (NameError)
The 'Module.nesting' Method
The nesting
method defined on the Module
returns the list of modules nested at the time of call. Following example shows how the two versions differ in their module nesting.
module A
p Module.nesting # [A]
module B
p Module.nesting # [A::B, A]
end
module B::C
p Module.nesting # [A::B::C, A]
end
end
module A::D
p Module.nesting # [A::D]
end
When module D is defined, the nesting does not include module A. Hence, module D cannot access the constants defined in module A, without explicitly using the scope resolution (::
) operator.
That's a wrap. I hope you found this article helpful and you learned something new.
As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.
If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.