Modules in Ruby
As a named collection of methods, constants, and class variables, modules are a basic organizational tool in programming.
Like classes, they have a similar structure and are defined using the module keyword. Modules cannot be subclassed or instantiated (you cannot generate an object directly from a module), in contrast to classes. All classes are technically modules since, interestingly, the Class structure is a subclass of Module.
Modules are used for two main functions: supplying Mixins and serving as Namespaces. Modules and hashes have been conceptually compared.
Modules as Namespaces
Related methods and constants are grouped under a single name in a module, which acts as a container to avoid name clashes (or collisions). This is very helpful for creating methods that don’t need to be written in an object-oriented manner.
Module names must start with a capital letter, much like class names.
Code Example: Defining a Namespace
The self.
prefix is used in this example to designate methods as “class methods” of the module, and constants are defined inside the module body:
module Base64
# The constant is now correctly defined to hold the full set of Base64 digits.
DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# The method now takes a parameter called 'data'.
def self.encode(data)
# This is a simplified, non-functional placeholder.
# In a real implementation, this would perform the encoding.
"Encoded: #{data}"
end
# The method is now defined using `self.decode`.
def self.decode(data)
# This is a placeholder for the decoding logic.
"Decoded: #{data}"
end
end
# Define some data to work with.
original_data = "Hello, world!"
# Now, we call the `encode` method and store the result.
encoded_text = Base64.encode(original_data)
# Finally, we print the output to the console.
puts encoded_text
# => Encoded: Hello, world!
# You could also call the decode method.
decoded_text = Base64.decode(encoded_text)
puts decoded_text
# => Decoded: Encoded: Hello, world!
Output
Encoded: Hello, world!
Decoded: Encoded: Hello, world!
Modules as Mixins
The “mixin” feature allows modules to exchange functionality across classes, hence removing the need for conventional multiple inheritance. When the module is used as a mixin, instance methods are defined.
Using (Instance Methods)
The include
keyword is used to mix in a module into a class definition. As a result, the instance methods of the module are accessible as class instance methods.
module SomeMixin
def foo
puts "foo!"
end
end
class Bar
include SomeMixin
def baz
puts "baz!"
end
end
b = Bar.new
b.baz
b.foo
Output
baz!
foo!
The Enumerable module, which defines a number of helpful iterators, is a perfect illustration. Enumerable can be used to obtain free methods like collect, detect, inject, and sort in any class that specifies the necessary methods.
Using (Class Methods)
To explicitly add the instance methods of a module as singleton methods to a particular object, use the Object.extend
method. The methods in the module turn into class methods for that class if this object also happens to be a class instance itself.
module Humor
def tickle
"hee, hee!"
end
end
class Grouchy
# Adds 'tickle' as a class method
extend Humor
end
# The `puts` command will print the return value of Grouchy.tickle
puts Grouchy.tickle # => "hee, hee!"
# This line is commented out because it will raise a NoMethodError
# a = Grouchy.new
# puts a.tickle
Output
hee, hee!
Module Functions
The module_function method can be used if you want a module’s methods to be available over the namespace as well as for mixing in (that is, callable as global-style functions after inclusion). Calling module_function turns the original instance methods private and creates copies of the provided methods in public class methods.
This is how the built-in math module functions:
# Using Math through the namespace:
puts Math.sin(0)
# Using Math as a function after inclusion:
include Math
puts sin(0)
Output
0.0
0.0
It is generally advised to avoid using self
in the method body when designing such a module function because the value of self
will vary depending on how the function is called (either implicitly after inclusion or through the module name).
A program can load modules by utilizing the global function require
.
You can also read Constructors In Ruby: How The Initialize Method Works