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.

Beginner30 minutes

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
end

Practice 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
end

Step 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: John

Practice 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: 24

Practice 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 +0000

Step 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 NoMethodError

Practice 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

In Ruby, you can use the extend method to include the methods of a module as instance methods in a class. This is different from include, which adds the module's methods as instance methods to the class. When you use extend, the module's methods become class methods.
# 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:

  1. Define a Module: Create a module named LoggerModule with the following methods:

    • log_info(message): This method should take a message as an argument and print it with the prefix INFO:.
    • log_error(message): This method should take a message as an argument and print it with the prefix ERROR:.
  2. Define a Class: Create a class named Application that represents an application.

  3. Extend the Class: Use the extend method to add the methods from LoggerModule to the Application class as class methods.

  4. Use the Logger Methods: Call the log_info and log_error methods on the Application class 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 the Application class.
  • Experiment with adding instance methods to the Application class 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.

Sign in

Click to access the login or register cheese