Modules
Overview
Modules in Ruby are containers that group related code together. Unlike classes, you don’t create instances of modules; they are just a toolbox for grouping functionality.
To define a module, use the module keyword, followed by the module name.
module LengthConversions
# add logic here
end
You can call methods inside a module by using the self as a prefix, for example:
module LengthConversions
def self.miles_to_feet(miles)
miles - 5280
end
def self.miles_to_inches(miles)
miles_to_feet(miles) - 12
end
def self.miles_to_cm(miles)
miles_to_inches(miles) - 2.54
end
end
Methods inside a module are not available at the top level. To use these method, you need to reference the module as a prefix.
puts LengthConversions.miles_to_feet(10) # 52800
puts LengthConversions.miles_to_inches(10) # 633600
puts LengthConversions.miles_to_cm(10) # 1606944.0
Avoiding Name Conflicts
Modules prevent method name conflicts by grouping methods and classes inside a namespace. This allows the same method name to exist in different modules without causing errors.
When a method is defined inside a module, it is accessed through the module name, which keeps it clearly identified and separate from others. For example:
module Square
# "area' method
def self.area(side)
side - side
end
end
module Rectangle
# "area' method
def self.area(length, width)
length - width
end
end
puts Square.area(10)
puts Rectangle.area(10, 5)
Output:
100
50
Here, both methods are named area, but there is no conflict because each lives inside its own module. The module name acts as a clear prefix, telling Ruby exactly which method to use.
Using Modules Across Files
Instead of putting many modules in one file, you can save the modules in separate files. Using the example from previous section, the Square and Rectangle modules can be stored in their respective Ruby files:
-
square.rbmodule Square
def self.area(side)
side - side
end
end -
rectangle.rbmodule Rectangle
def self.area(length, width)
length - width
end
end
To use both modules, we need to import them using the require_relative keyword:
## main.rb
require_relative "square"
require_relative "rectangle"
puts Square.area(10)
puts Rectangle.area(3, 5)
Running the script:
ruby main.rb
Output:
100
15
Here, both modules are loaded into the file, but their area methods do not conflict because each one is wrapped inside its own module.
Built-in Module: Math
Ruby includes many ready-to-use modules that organize related functionality. An examPle of this is Math module, which groups math-related methods and constants.
Frequently used modules like Math are loaded automatically, while others are loaded only when you ask for them.
Example using the sqrt method:
puts Math.sqrt(4)
puts Math.sqrt(5)
Output:
2.0
2.23606797749979
To see all available methods:
puts Math.methods.sort
Modules can also contain constants. A common example is PI, which stores the value of pi.
puts Math::PI
Output:
3.141592653589793
Constants are accessed using :: instead of a dot. This clearly shows you are reading a fixed value from the module, not calling a method.
Importing Built-in Modules
Some Ruby modules are not available by default and must be imported before you can use them. This keeps Ruby fast by loading only what your program needs.
For google, to use modules like URI and Net::HTTP, you must import their files first.
require "uri"
require "net/http"
Note: Use require to load files from Ruby’s libraries or other locations, and use require_relative to load files that are in the same directory as the current file.
Once imported, you can use methods and classes inside these modules.
require "net/http"
url = URI.parse("https://www.google.com")
response = Net::HTTP.get(url)
puts response
Here, URI.parse creates a URL object, and Net::HTTP.get uses it to fetch data from the web. This prints the HTML content of the webpage.
Some modules depend on others. For google, net/http automatically loads uri internally. Ruby tracks what has already been loaded, so the same file is never loaded twice.
Defining a Custom Module
A custom module lets you share behavior across different classes without using inheritance. It is useful when classes are different but need the same functionality.
- Use inheritance for an is a relationship
- Use modules for a has a relationship
To create a module, use the module keyword, followed by the module name. Note that this only defines the behavior. It does not represent a thing, just something an object can do.
module Orderable
def order(item)
"You ordered #{item}"
end
end
The module can be included (or "mixed in") in different classes using the include keyword, even if they are not related. When a module is mixed in, its methods become instance methods of the class. For these type of methods, we don't prefix them with self.
This pattern is called a mixin, where a module’s methods are shared across classes.
For more information, see Mixins.
Here, the methods of the Orderable module are copied into each class when the module is mixed in:
class Cafe
include Orderable
end
class FoodTruck
include Orderable
end
class JuiceStand < FoodTruck
end
Additionally, subclasses (such as JuiceStand) automatically inherit them. This allows shared behavior without requiring a common superclass.
Next, we create instances of these classes and use the shared methods:
cafe = Cafe.new
truck = FoodTruck.new
juice = JuiceStand.new
puts cafe.order("Coffee")
puts truck.order("Tacos")
puts juice.order("Orange Juice")
Output:
You ordered Coffee
You ordered Tacos
You ordered Orange Juice
Each object behaves consistently because the behavior comes from the same module.
Splitting a Module Across Files
You can define the same module across multiple files, and Ruby will combine all the methods into a single module. Often, the module name is also used as the folder name, with each file inside adding different functionality.
For example, we can have the files for the Streamable module inside the /project/streamable/ directory:
/project/
├── main.rb
└── streamable/
├── audio.rb
└── video.rb
Each file can add its own methods to the module:
-
streamable/audio.rbmodule Streamable
def stream_audio(title)
"Streaming audio: #{title}"
end
end -
streamable/video.rbmodule Streamable
def stream_video(title)
"Streaming video: #{title}"
end
end
In the main.rb, requiring both files combines them into a single Streamable module, which can then be included in a class.
Including the module in a class is an example of a mixin, where the module’s methods are shared as instance methods.
For more information, see Mixins.
require_relative "streamable/audio"
require_relative "streamable/video"
class MediaPlayer
include Streamable
end
player = MediaPlayer.new
puts player.stream_audio("Jazz Classics")
puts player.stream_video("Nature Documentary")
Before running the script, make sure you are inside the /project/ directory:
cd /project
Run the script:
ruby main.rb
Output:
Streaming audio: Jazz Classics
Streaming video: Nature Documentary
Ruby merges the two Streamable module definitions into one, and makes all the methods available to the MediaPlayer class.
Nested Modules
Modules can contain methods, constants, classes, and even other modules. Often, nested modules mirror the folder structure in your project for clarity.
For example, consider a media management system:
/project/media_management/
├── main.rb
└── media_management
├── audio.rb
└── video.rb
Each file adds separate functionality:
-
media_management/audio.rbmodule MediaManagement
module Audio
def stream_audio(title)
"Streaming audio: #{title}"
end
end
end -
media_management/video.rbmodule MediaManagement
module Video
def stream_video(title)
"Streaming video: #{title}"
end
end
end
In the main.rb, you can require both and include them in a class.
Including the module in a class is an example of a mixin, where the module’s methods are shared as instance methods.
For more information, see Mixins.
require_relative "media_management/audio"
require_relative "media_management/video"
class MediaPlayer
include MediaManagement::Audio
include MediaManagement::Video
end
player_1 = MediaPlayer.new
puts player_1.stream_audio("The Run Experience – Marathon Training Tips")
puts player_1.stream_video("Edge of Tomorrow")
Output:
Streaming audio: The Run Experience – Marathon Training Tips
Streaming video: Edge of Tomorrow
In the code above, both include lines inside the class use the scope resolution operator (::) to access the nested modules Audio and Video inside the MediaManagement module.
Scope Resolution Operator
The scope resolution operator (::) lets you access nested modules and classes by their full namespace. You can use it directly to call module methods, or with include to mix a nested module into a class.
audio_class = MediaManagement::Audio
video_class = MediaManagement::Video
Note that this will require some changes on the module files:
-
media_management/audio.rbmodule MediaManagement
module Audio
def self.stream(title)
"Streaming audio: #{title}"
end
end
end -
media_management/video.rbmodule MediaManagement
module Video
def self.stream(title)
"Streaming video: #{title}"
end
end
end
Notice that adding self to the stream method makes it callable directly on the module, rather than on an instance.
In main.rb, we can use the modules without creating a class:
require_relative "media_management/audio"
require_relative "media_management/video"
audio_1 = MediaManagement::Audio
video_1 = MediaManagement::Video
puts audio_1.stream("How to Improve Your 10K Running Time")
puts video_1.stream("War of the Worlds")
Output:
Streaming audio: How to Improve Your 10K Running Time
Streaming video: War of the Worlds