Skip to content
AgileLogics
6 min read

Modules: Namespaces and Mixins

Prerequisites: Polymorphism and duck typing

A module is a container for methods and constants. Unlike a class, you can't create objects from a module. Modules have two main jobs.

1. Namespaces — grouping related code

A module keeps names from clashing by giving them a prefix:

module Billing
  TAX_RATE = 0.2

  class Invoice
    def initialize(amount) = @amount = amount

    def total = @amount * (1 + TAX_RATE)
  end

  def self.format(amount) = "$#{'%.2f' % amount}"
end

invoice = Billing::Invoice.new(50)
puts Billing.format(invoice.total)
$60.00

Billing::Invoice can't collide with some other library's Invoice class. Almost every gem wraps its code in a module named after itself.

2. Mixins — sharing behavior

include copies a module's methods into a class as instance methods. This is called a mixin, and it's how Ruby shares abilities between unrelated classes without inheritance:

module Greetable
  def greet
    "Hi, I'm #{name}"
  end
end

module Timestamped
  def created_label
    "created #{created_on}"
  end
end

class User
  include Greetable
  include Timestamped
  attr_reader :name, :created_on

  def initialize(name, created_on)
    @name = name
    @created_on = created_on
  end
end

class Robot
  include Greetable
  def name = "R2"
end

user = User.new("Ada", "2025-01-15")
puts user.greet
puts user.created_label
puts Robot.new.greet
p User.ancestors.first(3)
Hi, I'm Ada
created 2025-01-15
Hi, I'm R2
[User, Timestamped, Greetable]

Notice that Greetable#greet calls name — the module assumes the class provides it. That's duck typing again: a module can rely on any method the including class has.

ancestors shows the method lookup chain: Ruby looks for a method in User first, then in its included modules (most recently included first), then up the superclass chain.

extend adds a module's methods to a single object (or to a class as class methods) instead of to every instance.

Common mistakes

  • Using inheritance just to share a helper method. If two classes aren't "kinds of" the same thing, a mixin is a better fit.
  • Mixing in large modules with lots of hidden assumptions about the class. Keep mixins small and document what they expect.

Practice

Write a Describable module with a describe method that returns "#{self.class.name}: #{title}". Include it in Book and Movie classes that each have a title.

Show solution
module Describable
  def describe = "#{self.class.name}: #{title}"
end

class Book
  include Describable
  attr_reader :title
  def initialize(title) = @title = title
end

class Movie
  include Describable
  attr_reader :title
  def initialize(title) = @title = title
end

puts Book.new("Dune").describe
puts Movie.new("Up").describe
Book: Dune
Movie: Up