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
endUsing 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
Railsdoesn't exist, Ruby will create it first, then define moduleActiveJob. If moduleRailsalready exists, then it will re-open it and define moduleActiveJobinside it.
module Rails
module ActiveJob
end
end- Use the
::operator to define the second module on the same line.
module Rails::ActiveJob
endThere are three ways in which the second version (Rails::ActiveJob) differs from the first:
- It assumes that the outer module, i.e.
Railsis already defined. If it's not, Ruby will throw aNameError: uninitialized constant Railserror. So use this syntax only if you're sure that moduleRailsexists. - 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
Railsinside moduleActiveJobwithout 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.2You 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.2Let'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.2Trying 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.2However, 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]
endWhen 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.