#---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.)

Module Mixin

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.

Syntax

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

Include a Module

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.

Method Lookup Order

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).

Prepend a Module

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]

Extend a Module

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"]

How to use Modules

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.

Standalone Modules

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:

  • Define the methods using self.method_name, like so:
module Trainable
  def self.method1
    'method1'
  end
end
p Trainable.method1 # => "method1"
  • Define the methods using module_function, like so:
module Trainable
  def method2
    'method2'
  end
  module_function :method2
end
p Trainable.method2 # => "method2"
  • Define the methods using extend self, like so:
module Trainable
  extend self
  def method3
    'method3'
  end
end
p Trainable.method3 # => "method3"

Namespaces

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.

Namespacing Example

See this enumerators.rb file from Sidekiq:

  • The ‘Enumerators’ module in it is defined inside multiple modules like so:
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
  • The top-level ‘Sidekiq’ module acts as the container for all things sidekiq related. It’s created first in the ‘lib/sidekiq.rb’ file.
  • Next is the ‘Job’ module, whose child modules are all present in separate files in the sidekiq/job directory.
p Sidekiq::Job.constants
  • Finally, the ‘Iterable’ module is defined inside the -‘Job’_ module, which defines a bunch of modules and constants. The ‘Enumerators’ module above is one of them.
p Sidekiq::Job::Iterable.constants
  • Enumerable - a cool module that’s included in a bunch of core Ruby classes like Array, Hash, Dir etc to provide useful functionalities for sorting, querying, searching and iterating.
p [Array, Hash].all? { it.ancestors.include?(Enumerable) }
## => true
  • Comparable - a module that provides comparison methods like <, >, == etc. It’s included in the Numeric class and many other classes.
  • Kernel - a module that provides methods like p, puts, print, gets etc. It’s included in the Object class, which means all objects in Ruby have access to these methods.

Official Docs