Ruby Modules — include extend prepend and Namespace Organization Explained
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