Ruby Modules
Ruby Modules: Namespacing and Mixins
Section titled “Ruby Modules: Namespacing and Mixins”Modules in Ruby serve two primary purposes:
- Namespacing: They provide a way to group related methods, classes, and constants, preventing name collisions between different parts of your codebase or with third-party libraries.
- Mixins: They allow you to share common functionality (instance methods) among multiple classes without using traditional inheritance, offering a form of multiple inheritance.
Syntax for Defining a Module:
Section titled “Syntax for Defining a Module:”A module is defined using the module keyword, followed by the module name (conventionally CamelCase), and ends with end.
module MyLogger PI = 3.14159 # A constant within the module
# A module method (called on the module itself) def self.log(message) puts "[LOG] #{Time.now}: #{message}" end
# An instance method (intended for mixing into classes) def print_info(text) "INFO: #{text}" endendAccessing Module Constants and Methods:
Module constants are accessed using the :: (scope resolution) operator: MyLogger::PI.
Module methods (defined with self. or ModuleName.) are called directly on the module: MyLogger.log("Application started").
Ruby require and require_relative Statements:
Section titled “Ruby require and require_relative Statements:”To use a module defined in a separate file, you need to load that file. Ruby provides two main ways to do this:
require 'filename': Loads a file. Ruby searches for this file in directories listed in the$LOAD_PATH(an array of paths). It’s typically used for loading installed gems or standard library features. You usually omit the.rbextension.require_relative 'filename': Loads a file relative to the path of the file containing therequire_relativestatement. This is preferred for loading your own project files. You usually include the.rbextension or omit it if it’s clear.
Example of Namespacing:
Section titled “Example of Namespacing:”Imagine two different libraries defining a Utility module.
module Payments module Utility def self.format_currency(amount) "$%.2f" % amount end endend
# File: reporting_utility.rbmodule Reporting module Utility def self.generate_report_name(base_name) "#{base_name}_#{Time.now.strftime('%Y%m%d')}.pdf" end endend
# File: main_app.rbrequire_relative 'payments_utility'require_relative 'reporting_utility'
puts Payments::Utility.format_currency(123.45) # Output: $123.45puts Reporting::Utility.generate_report_name("sales") # Output: sales_20230315.pdf (date will vary)This demonstrates how modules prevent name clashes. Payments::Utility is distinct from Reporting::Utility.
Ruby include Statement: Mixins
Section titled “Ruby include Statement: Mixins”The include keyword is used to mix a module’s instance methods and constants into a class. When a class includes a module, the module’s instance methods become instance methods of the class.
Example of include for Mixins:
module Printable # This instance method will be mixed into any class that includes Printable def pretty_print "<#{self.class} - #{self.object_id}> Attributes: #{instance_variables.map { |var| "#{var}=#{instance_variable_get(var).inspect}" }.join(', ')}" endend
# File: book.rbrequire_relative 'printable'
class Book include Printable # Mixes in the pretty_print method from Printable module
attr_accessor :title, :author
def initialize(title, author) @title = title @author = author endend
class Magazine include Printable attr_accessor :name, :issue_date
def initialize(name, issue_date) @name = name @issue_date = issue_date endend
# File: app.rbrequire_relative 'book'require_relative 'magazine'
my_book = Book.new("The Great Gatsby", "F. Scott Fitzgerald")my_magazine = Magazine.new("Tech Weekly", "2023-10-26")
puts my_book.pretty_print# Example Output: <Book - 701...> Attributes: @title="The Great Gatsby", @author="F. Scott Fitzgerald"
puts my_magazine.pretty_print# Example Output: <Magazine - 702...> Attributes: @name="Tech Weekly", @issue_date="2023-10-26"In this example, both Book and Magazine classes gain the pretty_print functionality without inheriting from a common superclass for this specific behavior. This is the power of mixins.
Constants from an included module are also accessible directly within the class (e.g., if Printable had a constant VERSION, Book could access it as VERSION).
Ruby extend Statement: Adding Class Methods
Section titled “Ruby extend Statement: Adding Class Methods”While include adds a module’s methods as instance methods, extend adds them as class methods to the class.
module ClassUtils def find(id) # Hypothetical find logic for a class method puts "#{self.name} finding record with id: #{id}" # new(id: id) # for example endend
# File: user.rbrequire_relative 'class_utils'
class User extend ClassUtils # Adds ClassUtils methods as class methods to User
attr_accessor :id, :name def initialize(id: nil, name: nil) @id = id @name = name endend
# File: app.rbrequire_relative 'user'
User.find(101) # Calls the 'find' method as a class method on User# Output: User finding record with id: 101Ruby prepend Statement (Advanced):
Section titled “Ruby prepend Statement (Advanced):”Introduced in Ruby 2.0, prepend is similar to include but inserts the module in the inheritance chain before the class itself. This means methods in the prepended module can override methods in the class and can use super to call the class’s original method. It’s a more advanced feature often used for method decoration or modifying behavior in a more intrusive way than include.
module LoggingPrepender def save puts "[PREPEND LOG] Before saving #{self.class.name}..." result = super # Calls the original 'save' method of the class puts "[PREPEND LOG] After saving #{self.class.name}... Result: #{result}" result endend
class Document prepend LoggingPrepender
def save puts "Document saving..." true # Simulate successful save endend
doc = Document.newdoc.save# Output:# [PREPEND LOG] Before saving Document...# Document saving...# [PREPEND LOG] After saving Document... Result: truePractical Applications:
Modules are extensively used in Ruby. The entire Ruby standard library is organized into modules (e.g., Math, File, Enumerable). Rails framework components like ActiveRecord::Base or ActionController::Base include numerous modules to provide their rich feature sets. You’ll use modules to organize your own code, share utility functions, and implement cross-cutting concerns like logging or serialization.