Skip to content

Ruby Modules — include extend prepend and Namespace Organization Explained

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Ruby Modules. We cover key concepts, practical examples, and best practices to help you master this topic.

Ruby modules organize reusable code through include (instance methods as mixins), extend (class methods), prepend (method override priority), and namespace containers for avoiding name collisions.

What You'll Learn

  • Creating modules as namespaces and mixins
  • include vs extend vs prepend — when to use each
  • Module organization patterns for large codebases
  • The method lookup chain and how modules affect it

Why It Matters

Modules solve Ruby's single-inheritance limitation and provide the primary mechanism for code reuse. Durga Antivirus Pro uses modules for shareable scan strategies, logging, and configuration. DodaZIP modules define compressors, encryptors, and file filters. Rails itself is built on modules — every concern, helper, and concern is a module.

Real-World Use

A Rails application uses modules for shared behavior (like Authenticatable, Taggable, Commentable), namespace organization (API::V1::UsersController), and helper methods in views. Sinatra uses modules for extension. Understanding modules is essential for idiomatic Ruby.

flowchart LR
    A["Modules"] --> B["Namespace"]
    B --> C["Mixins"]
    C --> D["Include vs Extend"]
    D --> E["Prepend"]
    E --> F["Inheritance"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Modules as Namespaces

Preventing name collisions in large codebases:

module Admin
  class User
    def initialize(name)
      @name = name
    end

    def role
      "admin"
    end
  end
end

module Public
  class User
    def initialize(name)
      @name = name
    end

    def role
      "user"
    end
  end
end

admin = Admin::User.new("Alice")
public = Public::User.new("Bob")

puts admin.role   # admin
puts public.role  # user

Nested Modules

module API
  module V1
    class UsersController
      def index
        "Listing users via API v1"
      end
    end
  end
end

controller = API::V1::UsersController.new
puts controller.index  # Listing users via API v1

Module Methods (Module Functions)

module MathUtils
  def self.square(n)
    n * n
  end

  def self.cube(n)
    n ** 3
  end
end

puts MathUtils.square(5)  # 25
puts MathUtils.cube(3)    # 27

Modules as Mixins (include)

The include statement adds module methods as instance methods in the including class:

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

class Person
  include Greetable

  attr_reader :name

  def initialize(name)
    @name = name
  end
end

alice = Person.new("Alice")
puts alice.greet  # Hello, I'm Alice

Multiple Includes

module Walkable
  def walk
    "#{name} is walking"
  end
end

module Swimmable
  def swim
    "#{name} is swimming"
  end
end

class Person
  include Walkable
  include Swimmable

  attr_reader :name

  def initialize(name)
    @name = name
  end
end

alice = Person.new("Alice")
puts alice.walk  # Alice is walking
puts alice.swim  # Alice is swimming

Extend (Adding Class Methods)

module ClassMethods
  def species
    "Homo sapiens"
  end
end

class Person
  extend ClassMethods
end

puts Person.species  # Homo sapiens

Using Both Include and Extend

The common pattern for modules that add both instance and class methods:

module Taggable
  # This runs when the module is included
  def self.included(base)
    base.extend(ClassMethods)
  end

  # Instance methods
  def tags
    @tags ||= []
  end

  def add_tag(tag)
    tags << tag
  end

  # Class methods
  module ClassMethods
    def tag_limit(limit)
      @tag_limit = limit
    end
  end
end

class Article
  include Taggable
  tag_limit 10
end

article = Article.new
article.add_tag("ruby")
article.add_tag("programming")
puts article.tags.inspect  # ["ruby", "programming"]
puts Article.tag_limit     # nil (since @tag_limit is on the class)

Prepend

prepend inserts the module before the class in the method lookup chain:

module Audit
  def save
    puts "Audit: about to save"
    super  # Call the original save
    puts "Audit: saved successfully"
  end
end

class Document
  prepend Audit

  def save
    puts "Document: saving..."
  end
end

doc = Document.new
doc.save
# Audit: about to save
# Document: saving...
# Audit: saved successfully

With include, the class method would run first. With prepend, the module method wraps the class method.

Method Lookup Chain

Understanding how Ruby finds methods:

module A
  def who
    "Module A"
  end
end

module B
  def who
    "Module B"
  end
end

class Base
  def who
    "Base class"
  end
end

class Derived < Base
  include A
  prepend B

  def who
    "Derived class"
  end
end

obj = Derived.new
puts obj.who  # "Derived class"

# Chain: obj -> B (prepended) -> Derived -> A (included) -> Base -> Object

Checking the Ancestor Chain

puts Derived.ancestors.inspect
# [Derived, B, A, Base, Object, Kernel, BasicObject]

Using Module Methods from Other Modules

module MathOps
  def self.factorial(n)
    (1..n).reduce(1, :*)
  end
end

module Calculator
  def self.combination(n, r)
    MathOps.factorial(n) / (MathOps.factorial(r) * MathOps.factorial(n - r))
  end
end

puts Calculator.combination(5, 2)  # 10

Module Constants

Modules define their own scope for constants:

module Config
  APP_NAME = "MyApp"
  VERSION = "1.0.0"
  DEFAULT_LIMIT = 100
end

puts Config::APP_NAME    # MyApp
puts Config::VERSION     # 1.0.0

Module Pattern: Callable

A module that responds to call for functional-style programming:

module Double
  def self.call(value)
    value * 2
  end
end

module Square
  def self.call(value)
    value ** 2
  end
end

[1, 2, 3].map(&Double)   # [2, 4, 6]
[1, 2, 3].map(&Square)   # [1, 4, 9]

Common Mistakes

1. Confusing include and extend

module M
  def greet
    "Hello"
  end
end

class A
  include M  # greet becomes instance method
end

class B
  extend M   # greet becomes class method
end

puts A.new.greet  # Hello
puts B.greet      # Hello
# puts B.new.greet  # NoMethodError

2. Forgetting to Use :: for Namespace Access

module Admin
  class User
  end
end

# Wrong
user = Admin::User.new

# Also wrong inside a different namespace without full path
module API
  # User here refers to ::User, not Admin::User
end

3. Module Method vs Mixin Confusion

module Math
  def self.square(n)  # Module method
    n * n
  end

  def cube(n)  # Mixin method (needs include)
    n ** 3
  end
end

puts Math.square(5)    # 25
# puts Math.cube(5)     # NoMethodError

class Calculator
  include Math
end

puts Calculator.new.cube(5)  # 125

4. Circular Module Dependencies

Avoid modules that include each other — this creates infinite loops at load time.

5. Using Extend When You Mean Include

module Greetable
  def greet
    "Hello"
  end
end

class Person
  extend Greetable  # greet is a class method now
end

# Person.new.greet  # NoMethodError
Person.greet       # "Hello" — probably not what you wanted

Practice Questions

1. What's the difference between include and extend?

include adds module methods as instance methods in the class. extend adds module methods as class methods. Use include for shared behavior across instances, extend for class-level functionality.

2. What does prepend do differently from include?

prepend inserts the module before the class in the method lookup chain. If the module defines a method that exists in the class, the module method runs first and can call super to invoke the class method. Include places the module after the class.

3. How do you create a namespace with modules?

Define a module with class names inside: module Admin; class User; end; end. Access with Admin::User. Namespaces prevent name collisions between different contexts.

4. What is the ancestors method?

Class.ancestors returns the method lookup chain as an array. It shows the order in which Ruby searches for methods: the class, prepended modules, the class's methods, included modules, the superclass, and so on up to BasicObject.

Challenge: Create a Formattable module that can be included in any class to add to_csv, to_json, and to_table output methods, using the class's instance variables for data.

Solution
require 'json'

module Formattable
  def to_csv
    headers = instance_variables
    values = headers.map { |var| instance_variable_get(var) }
    "#{headers.join(',')}\n#{values.join(',')}"
  end

  def to_json
    hash = {}
    instance_variables.each do |var|
      hash[var.to_s.sub('@', '')] = instance_variable_get(var)
    end
    JSON.generate(hash)
  end

  def to_table
    headers = instance_variables.map { |v| v.to_s.sub('@', '') }
    values = instance_variables.map { |v| instance_variable_get(v) }
    separator = headers.map { '-' * _1.length }.join(' | ')
    "#{headers.join(' | ')}\n#{separator}\n#{values.join(' | ')}"
  end
end

class Product
  include Formattable

  def initialize(name, price, quantity)
    @name = name
    @price = price
    @quantity = quantity
  end
end

p = Product.new("Widget", 9.99, 100)
puts p.to_csv
puts p.to_json
puts p.to_table

FAQ

{{< faq question="Can I include a module in another module?" >}} Yes. Modules can include other modules. When you include a module in a class, all included modules from that module are also added to the lookup chain. {{< /faq >}}

{{< faq question="What's the difference between a module and a class?" >}} Modules cannot be instantiated (no Module.new). Modules have no inheritance. Modules are used for namespacing and mixins. Classes are for creating objects and support inheritance. {{< /faq >}}

{{< faq question="How do I call super inside a module method?" >}} super works inside methods that were added via include or prepend. It calls the next implementation in the method lookup chain. In prepend, super calls the original class method. {{< /faq >}}

{{< faq question="Can modules have instance variables?" >}} Module-level instance variables belong to the module itself, not to the including class. If a module method uses @var, it refers to the including object's @var when used as a mixin. {{< /faq >}}

{{< faq question="What is the difference between require and include?" >}} require loads a file into memory. include mixes a module's methods into a class. You require a file containing a module definition, then include the module to use it. {{< /faq >}}

Try It Yourself

# modules_demo.rb

module Debug
  def self.included(base)
    base.extend(ClassDebug)
  end

  module ClassDebug
    def describe
      "I am a #{name}"
    end
  end

  def debug
    "#{self.class.name}: #{self.instance_variables.map { |iv|
      "#{iv}=#{instance_variable_get(iv)}"
    }.join(', ')}"
  end
end

class Car
  include Debug

  def initialize(make, model)
    @make = make
    @model = model
  end
end

car = Car.new("Toyota", "Camry")
puts Car.describe   # I am a Car
puts car.debug       # Car: @make=Toyota, @model=Camry

Expected output:

I am a Car
Car: @make=Toyota, @model=Camry

What's Next

Now that you understand modules, explore inheritance to understand how classes share behavior through hierarchical relationships.

Topic Description Link
Ruby Inheritance < operator, super, ancestors chain {{< ref "11-inheritance" >}}
Ruby Blocks & Procs Blocks, yield, Proc.new, call {{< ref "12-blocks-procs" >}}
Python Modules Compare Python module system Python

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro