#---Module---
In Ruby, a module is a collection of methods and
constants. It serves 2 purposes:
1. It can be added to a class to enhance the
class’s functionality, and
2. It can be used to group related methods and constants
together in a namespace so that there are no name
clashes.
(A collection of methods and constants. In Ruby, modules and classes are also constants. So a module can have other classes/modules in it. Not just methods.)
While you can only inherit from one class in ruby, you can include any number of modules in a class. This is called module mixin. This bypasses the shortcoming of single inheritance, allowing you to compose a class from multiple modules.
A module is defined using the module
keyword like so:
require 'date'
module Trainable
MAX_TRAINING_DAYS = 10
def commands
%w(sit stand attack)
end
def training_done?(start_date)
(Date.today - start_date) > MAX_TRAINING_DAYS
end
end
To include a module in a class, use the include
keyword like so:
class Dog
include Trainable
end
p Dog.new.commands # => ["sit", "stand", "attack"]
p Dog::MAX_TRAINING_DAYS # => 10
p Dog.new.training_done?(Date.today) # => false
p Dog.instance_methods.include?(:commands) # => true
As you can see, including a module in a class, makes all the methods in the module available to the class as instance methods.
What if the Dog class too has a commands method?
Who’s commands would be called when a dog object
receives the commands message?
The one defined in the class (Dog#commands) would
be called.
class Dog
include Trainable
def commands
%w(play sing)
end
end
p Dog.new.commands # => ["play", "sing"]
This is because of the method lookup order of the
class. You can find that using ancestors
like so:
p Dog.ancestors
## => [Dog, Trainable, Object, Kernel, BasicObject]
This means when you call a method on an object, ruby
will first look for the method in its class, Dog.
If it doesn’t find it there, it will look for it
next in the Trainable module, then in Object class,
then in the Kernel module, and finally in
BasicObject class.
(It’ll throw the famous NoMethodError only if it
can’t find the method in any of these places.)
So, in our case, ruby was able to find the commands method right in the Dog class (which is the first item in the ancestor array), so it didn’t bother going up the ancestor chain to look for it in the Trainable module (which came after the Dog class).
You can also prepend a module to a class, and that too
will add the module methods as instance methods to the
class just like what include does.
The difference is in the method lookup order.
This time, the module methods will be looked up first:
class Dog
prepend Trainable
def commands
%w(play sing)
end
end
p Dog.new.commands # => ["sit", "stand", "attack"]
This is because the Trainable module is now the first item in the ancestor chain:
p Dog.ancestors
## => [Trainable, Dog, Object, Kernel, BasicObject]
You can also extend a module in a class using the
extend
keyword. This makes all the methods in the
module available to the class as class methods:
class Cat
extend Trainable
end
p Cat.commands
## => ["sit", "stand", "attack"]
If you want to add a module’s methods
as class methods to a class, you’ll extend the
module.
And if you want to add them as instance methods, you’ll
include or prepend the module depending on the
priority you want to give to the module methods.
You can also use a module’s methods and constants without including or extending it in a class. This is useful when you just want to group a set of related methods and constants together, and call them without having to instantiate any class. You can do this by calling the module methods directly on the module itself:
## Without 'rescue nil', this fails with NoMethodError.
p Trainable.commands rescue nil
There are a few ways to do this:
self.method_name
, like so:module Trainable
def self.method1
'method1'
end
end
p Trainable.method1 # => "method1"
module_function
, like so:module Trainable
def method2
'method2'
end
module_function :method2
end
p Trainable.method2 # => "method2"
extend self
, like so:module Trainable
extend self
def method3
'method3'
end
end
p Trainable.method3 # => "method3"
Modules are also used to namespace things in a project. This is useful when you want to group related classes or modules together, and avoid name clashes with other classes/methods/constants in the project:
## Probably in a file: pet_store/trainable.rb
module PetStore
module Trainable
TIME_TAKEN = 5 # seconds
def self.commands
%w(sit stand attack)
end
end
end
## Probably in a file: pet_store/non_trainable.rb
module PetStore
module NonTrainable
def self.just_be
"I just am."
end
end
end
And then these can be referenced like so:
p PetStore::Trainable.commands
p PetStore::NonTrainable.just_be
p PetStore::Trainable::TIME_TAKEN # => 5
Note: As mentioned above, modules can also have classes in them. The intention isn’t so you could include such modules in another class. Although you can do that, it might not make sense. Instead, the intention is to group related code together in a namespace.
See this enumerators.rb file from Sidekiq:
module Sidekiq
module Job
module Iterable
module Enumerators
## some code that enumerates
end
end
end
end
To access this module outside the Sidekiq library, you’ll do this:
p Sidekiq::Job::Iterable::Enumerators
p Sidekiq::Job.constants
p Sidekiq::Job::Iterable.constants
p [Array, Hash].all? { it.ancestors.include?(Enumerable) }
## => true