This tutorial provides a comprehensive guide on using modules for namespacing and sharing methods (include, extend) in Ruby. You will learn how to define modules, include them in classes, extend objects with module methods, and leverage modules for namespacing.
Step 1: Defining Modules
To define a module in Ruby, use the module keyword followed by the module name. Modules can contain methods, constants, and other module definitions.
module Greetings
def say_hello
puts "Hello!"
end
end
Modules serve as containers for related methods and constants, allowing you to organize your code and avoid naming conflicts.
module Utilities
def format_date(date)
date.strftime("%Y-%m-%d")
end
endPractice Exercise
Create a module called MathOperations with methods add, subtract, multiply, and divide. Each method should take two parameters and perform the corresponding mathematical operation.
Show Solution
module MathOperations
def add(a, b)
a + b
end
def subtract(a, b)
a - b
end
def multiply(a, b)
a * b
end
def divide(a, b)
a / b
end
endStep 2: Including Modules
To include a module in a class, use the include keyword followed by the module name. This allows instances of the class to have access to the methods defined in the module.
class MyClass
include Greetings
end
obj = MyClass.new
obj.say_hello # Output: Hello!
Including a module in a class mixes in the module's methods as instance methods of the class.
module Loggable
def log(message)
puts "[LOG] #{message}"
end
end
class User
include Loggable
def initialize(name)
@name = name
log("User created: #{name}")
end
end
user = User.new("John")
# Output: [LOG] User created: JohnPractice Exercise
Create a module called Validator with a method validate_email that takes an email as a parameter and returns true if the email is valid (contains '@' and '.' characters) and false otherwise. Include the Validator module in a User class and use the validate_email method in the initialize method to validate the provided email.
Show Solution
module Validator
def validate_email(email)
# Simple email validation regex
email.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
end
end
class User
include Validator
def initialize(name, email)
@name = name
if validate_email(email)
@email = email
puts "Valid email provided."
else
puts "Invalid email provided."
end
end
end
user1 = User.new("John", "john@example.com")
# Output: Valid email provided.
user2 = User.new("Jane", "jane@example")
# Output: Invalid email provided.Step 3: Extending Objects with Modules
To extend an object with module methods, use the extend keyword followed by the module name. This allows the object to have access to the methods defined in the module as singleton methods.
class MyClass
end
obj = MyClass.new
obj.extend(Greetings)
obj.say_hello # Output: Hello!
Extending an object with a module adds the module's methods as singleton methods to that specific object instance.
module Printable
def print_details
puts "Details: #{self.inspect}"
end
end
class Person
def initialize(name, age)
@name = name
@age = age
end
end
person = Person.new("Alice", 25)
person.extend(Printable)
person.print_details
# Output: Details: #Practice Exercise
Create a module called Serializable with a method to_json that returns a JSON representation of an object's instance variables. Extend an instance of a Product class (with instance variables @name, @price, and @quantity) with the Serializable module and call the to_json method to get the JSON representation of the product.
Show Solution
require 'json'
module Serializable
def to_json
JSON.generate(instance_variables.map { |var| [var[1..-1], instance_variable_get(var)] }.to_h)
end
end
class Product
def initialize(name, price, quantity)
@name = name
@price = price
@quantity = quantity
end
end
product = Product.new("Apple", 0.99, 100)
product.extend(Serializable)
puts product.to_json
# Output: {"name":"Apple","price":0.99,"quantity":100}Step 4: Namespacing with Modules
Modules can be used for namespacing to avoid naming conflicts and to organize related classes and modules. You can nest classes and modules inside a module to create a namespace.
module MyNamespace
class MyClass
# Class definition
end
module MyModule
# Module definition
end
end
obj = MyNamespace::MyClass.new
By nesting classes and modules inside a module, you create a namespace hierarchy, which helps prevent naming collisions and improves code organization.
module Shapes
class Circle
def initialize(radius)
@radius = radius
end
def area
Math::PI * @radius ** 2
end
end
class Rectangle
def initialize(length, width)
@length = length
@width = width
end
def area
@length * @width
end
end
end
circle = Shapes::Circle.new(5)
puts circle.area # Output: 78.53981633974483
rectangle = Shapes::Rectangle.new(4, 6)
puts rectangle.area # Output: 24Practice Exercise
Create a module called Banking with two nested classes: Account and Transaction. The Account class should have methods to deposit, withdraw, and check the balance. The Transaction class should have methods to record a transaction and display transaction details. Use the Banking module as a namespace for the classes.
Show Solution
module Banking
class Account
attr_reader :balance
def initialize(initial_balance = 0)
@balance = initial_balance
end
def deposit(amount)
@balance += amount
puts "Deposited $#{amount}. New balance: $#{@balance}"
end
def withdraw(amount)
if amount <= @balance
@balance -= amount
puts "Withdrawn $#{amount}. New balance: $#{@balance}"
else
puts "Insufficient funds. Current balance: $#{@balance}"
end
end
end
class Transaction
def initialize(account, amount, type)
@account = account
@amount = amount
@type = type
@timestamp = Time.now
end
def record
case @type
when :deposit
@account.deposit(@amount)
when :withdrawal
@account.withdraw(@amount)
end
end
def display_details
puts "Transaction: #{@type}"
puts "Amount: $#{@amount}"
puts "Timestamp: #{@timestamp}"
end
end
end
account = Banking::Account.new(1000)
transaction1 = Banking::Transaction.new(account, 500, :deposit)
transaction1.record
transaction1.display_details
transaction2 = Banking::Transaction.new(account, 200, :withdrawal)
transaction2.record
transaction2.display_details
# Output:
# Deposited $500. New balance: $1500
# Transaction: deposit
# Amount: $500
# Timestamp: 2023-05-25 10:00:00 +0000
# Withdrawn $200. New balance: $1300
# Transaction: withdrawal
# Amount: $200
# Timestamp: 2023-05-25 10:01:00 +0000Step 5: Modules as Mixins
Modules can be used as mixins to share behavior across multiple classes. By including a module in a class, you can add the module's methods to the class without using inheritance.
module Flyable
def fly
puts "I'm flying!"
end
end
class Bird
include Flyable
end
class Plane
include Flyable
end
bird = Bird.new
bird.fly # Output: I'm flying!
plane = Plane.new
plane.fly # Output: I'm flying!
Mixins allow you to define a set of methods in a module and include them in multiple classes that need that behavior, promoting code reuse and modularity.
module Swimmable
def swim
puts "Swimming!"
end
end
module Walkable
def walk
puts "Walking!"
end
end
class Animal
def eat
puts "Eating!"
end
end
class Dog < Animal
include Swimmable
include Walkable
end
class Fish < Animal
include Swimmable
end
dog = Dog.new
dog.eat # Output: Eating!
dog.walk # Output: Walking!
dog.swim # Output: Swimming!
fish = Fish.new
fish.eat # Output: Eating!
fish.swim # Output: Swimming!
# fish.walk # Raises NoMethodErrorPractice Exercise
Create a module called Notifiable with methods send_email and send_sms. Include the Notifiable module in two classes: User and Admin. Implement the send_email and send_sms methods to output different messages based on the class. Create instances of User and Admin and call the notification methods.
Show Solution
module Notifiable
def send_email
raise NotImplementedError, "#{self.class} must implement send_email method"
end
def send_sms
raise NotImplementedError, "#{self.class} must implement send_sms method"
end
end
class User
include Notifiable
def send_email
puts "Sending email to user..."
end
def send_sms
puts "Sending SMS to user..."
end
end
class Admin
include Notifiable
def send_email
puts "Sending email to admin..."
end
def send_sms
puts "Sending SMS to admin..."
end
end
user = User.new
user.send_email # Output: Sending email to user...
user.send_sms # Output: Sending SMS to user...
admin = Admin.new
admin.send_email # Output: Sending email to admin...
admin.send_sms # Output: Sending SMS to admin...
# By raising NotImplementedError in the Notifiable module methods,
# we enforce that the including classes (User and Admin) must
# implement their own versions of send_email and send_sms.Step 6: Use extend in a Ruby Class
# Define a module with some methods
module MyModule
def greet
"Hello from MyModule!"
end
def farewell
"Goodbye from MyModule!"
end
end
# Define a class
class MyClass
# Use extend to add the module's methods as class methods
extend MyModule
end
# Now you can call the module's methods on the class itself
puts MyClass.greet # Output: Hello from MyModule!
puts MyClass.farewell # Output: Goodbye from MyModule!Practice Exercise
Sure! Here's an exercise to help you practice using extend with modules in Ruby:
Exercise: Enhancing a Logger Class
Objective:
Create a module with logging methods and extend a class to use these methods as class methods.
Instructions:
Define a Module: Create a module named
LoggerModulewith the following methods:log_info(message): This method should take a message as an argument and print it with the prefixINFO:.log_error(message): This method should take a message as an argument and print it with the prefixERROR:.
Define a Class: Create a class named
Applicationthat represents an application.Extend the Class: Use the
extendmethod to add the methods fromLoggerModuleto theApplicationclass as class methods.Use the Logger Methods: Call the
log_infoandlog_errormethods on theApplicationclass to log some messages.
Example Output:
INFO: Application has started.
ERROR: An error occurred in the application.
Bonus:
- Add another method to the module, such as
log_warning(message), and use it in theApplicationclass. - Experiment with adding instance methods to the
Applicationclass and see how they interact with the class methods provided by the module.
This exercise will help you understand how to use extend to add functionality to a class in Ruby.
Show Solution
# Define a module with logging methods
module LoggerModule
def log_info(message)
puts "INFO: #{message}"
end
def log_error(message)
puts "ERROR: #{message}"
end
def log_warning(message)
puts "WARNING: #{message}"
end
end
# Define a class that represents an application
class Application
# Extend the class with LoggerModule to add its methods as class methods
extend LoggerModule
end
# Use the logger methods to log messages
Application.log_info("Application has started.")
Application.log_error("An error occurred in the application.")
Application.log_warning("This is a warning message.")Sign in to take Cornell notes on this lesson — they save automatically and stay with your account.