Ruby Modules and Mixins: Namespaces, Include, and Extend
Ruby modules serve two important purposes: they act as namespaces to organize your code, and they provide a way to share behavior between classes through mixins. In this tutorial, you’ll learn both patterns with practical examples.
intro context
If you are coming from a language that leans heavily on inheritance, modules can feel like a simpler way to share behavior. Ruby lets you keep related code together without forcing every class into the same hierarchy. That makes modules useful for helpers, policy objects, and small behavior bundles that you want to reuse in more than one place.
Modules also make code easier to scan. When you see a module name in a constant path or an include statement, you immediately know that the code is meant to be composed rather than inherited. That small signal helps larger codebases stay readable.
If you are writing a library, modules also give you a clean boundary for public API. You can tuck implementation details behind a namespace and only expose the constants or mixins you actually want other code to use. That keeps the surface area smaller and makes refactoring less risky.
Why use modules?
Before we dive into the syntax, let’s understand why modules matter. In Ruby, a class can only inherit from one parent. But what if you want to share functionality across unrelated classes? That’s where modules come in.
Modules let you:
- Group related classes and methods together
- Create namespaces to avoid name collisions
- Share behavior between classes without inheritance
Modules as Namespaces
A module can contain constants, methods, and even other classes. Grouping related code together keeps your codebase organized:
module MathUtils
class Calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
PI = 3.14159
def self.circle_area(radius)
PI * radius * radius
end
end
calc = MathUtils::Calculator.new
puts calc.add(2, 3) # => 5
puts MathUtils.circle_area(5) # => 78.53975
The :: operator accesses constants and classes inside a module. This prevents naming conflicts, your Calculator class won’t clash with Calculator from another library.
That namespace pattern is especially useful when a project grows past a handful of files. A module gives you a place to gather related classes, constants, and helper methods without exposing them at the top level. The result is less naming pressure and a codebase that is easier to navigate.
Another useful habit is to treat a namespace module like a folder in code form. If a feature gets larger, you can put helper classes, value objects, and constants beside each other under the same module. That makes it much easier to discover related code later, especially in a project with many small files.
A good namespace module also makes it obvious which parts of the code are public. Constants and class names that live inside the module are clearly part of that domain, while anything at the top level is easier to spot as a utility or an entry point. This separation becomes more valuable as more contributors touch the same codebase.
Module constants
Modules are great for storing constants that belong together logically:
module HTTPStatus
OK = 200
NOT_FOUND = 404
SERVER_ERROR = 500
def self.message(code)
messages = { OK => "OK", NOT_FOUND => "Not Found" }
messages[code] || "Unknown"
end
end
puts HTTPStatus::OK # => 200
puts HTTPStatus.message(404) # => Not Found
Constants in modules follow the convention of being UPPERCASE with underscores.
Unlike classes, modules cannot be instantiated and do not have a superclass chain in the same way. They exist purely to group related code together. When you see a constant like HTTPStatus::OK, the path tells you both where the value lives and what it represents, which makes the code self-documenting without extra comments.
Mixins: sharing behavior across classes
Ruby doesn’t support multiple inheritance, but mixins let you achieve similar results. Use include to add instance methods:
module Debuggable
def debug_info
"#{self.class} - #{instance_variables.map { |v| "#{v}=#{instance_variable_get(v)}" }.join(', ')}"
end
end
class User
include Debuggable
def initialize(name, email)
@name = name
@email = email
end
end
user = User.new("Alice", "alice@example.com")
puts user.debug_info
# => User - @name=Alice, @email=alice@example.com
Now every User instance has the debug_info method. You can include multiple modules in a single class. The include keyword inserts the module into the class’s ancestor chain, placing it right above the class itself in method lookup order. That means the module’s methods behave as though they were defined directly on the class.
Because Ruby resolves methods by walking the ancestor chain, any method defined in an included module can be overridden by defining it again in the including class. This gives you a clean way to provide default behavior through the module while letting each class customize the parts it needs to change.
module Timestampable
def created_at
@created_at ||= Time.now
end
def updated_at
@updated_at ||= Time.now
end
end
class Order
include Debuggable
include Timestampable
def initialize(item)
@item = item
end
end
order = Order.new("Widget")
puts order.debug_info
# => Order - @item=Widget
The include keyword inserts the module into the class’s ancestor chain, placing it right above the class itself in method lookup order. That means the module’s methods behave as though they were defined directly on the class. You can include multiple modules in a single class, and Ruby resolves method lookup by walking the ancestor chain in order.
Extending with class methods
Use extend to add module methods as class methods:
module Configurable
def configure(&block)
@config ||= {}
block.call(@config) if block_given?
@config
end
def config
@config || {}
end
end
class API
extend Configurable
end
API.configure do |c|
c[:base_url] = "https://api.example.com"
c[:timeout] = 30
end
puts API.config # => {:base_url=>"https://api.example.com", :timeout=>30}
The extend keyword makes the module’s methods available as class methods on the extended class.
This pattern is handy when you want one shared module to provide both instance behavior and class-level helpers. Many Ruby frameworks use that split so the class itself can register configuration while each instance still gets the reusable methods.
The important design question is whether the module is describing behavior or acting like a registration hook. Behavior belongs in instance methods. Setup work that only makes sense at the class level belongs in the extend path or in a small included callback.
Combining include and extend
You can use both in the same module to provide both instance and class methods:
module Factory
def create(*args)
new(*args)
end
module ClassMethods
def brand_name
@brand_name ||= "Generic"
end
def brand_name=(name)
@brand_name = name
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
class Widget
include Factory
end
widget = Widget.create("Button")
puts Widget.brand_name # => Generic
The self.included callback runs when the module is included. This is a common pattern in Ruby frameworks like Rails, ActiveRecord uses this to add both instance methods and class methods through modules.
The prepend method
Ruby also provides prepend, which works like include but places the module’s methods earlier in the method lookup chain:
module UppercaseName
def name
">> #{super} <<"
end
end
class Person
prepend UppercaseName
def name
"Alice"
end
end
puts Person.new.name # => >> Alice <<
With prepend, the module’s method overrides the class’s method, but you can still call super to access the original.
The choice between include and prepend depends on whether you want the module to act as a fallback or an interceptor. With include, the class wins by default and the module fills in missing methods. With prepend, the module runs first and can decide whether to call super to reach the original. This makes prepend a natural fit for logging, instrumentation, and authorization wrappers where you want to intercept every call before it reaches the real method.
Real-world example: serialization
Here’s how you might use modules in a real application:
module Serializable
def to_json
hash = {}
instance_variables.each do |var|
hash[var.to_s.delete('@')] = instance_variable_get(var)
end
hash.to_json
end
end
class User
include Serializable
def initialize(name, role)
@name = name
@role = role
end
end
user = User.new("Bob", "admin")
puts user.to_json
# => {"name":"Bob","role":"admin"}
When to use mixins
Mixins work well for:
- Shared behavior across unrelated classes (like
Debuggable) - Adding utility methods (like
Timestampable) - Implementing interfaces (Ruby uses duck typing, so no formal interfaces)
- Adding cross-cutting concerns (like logging, caching)
Avoid overusing mixins, if you find yourself including many modules with unrelated functionality, consider inheritance or composition instead.
One practical rule is to ask whether the module represents a role or a true dependency. A role, like logging or timestamping, is a good fit for a mixin. A dependency with real state or lifecycle usually fits better as a separate object.
If you have to teach a new teammate why a module exists, the explanation should be short. If the answer sounds like three different responsibilities, the module may be doing too much and should probably be split into smaller pieces.
When not to use mixins
Don’t use mixins when:
- You need shared state (modules can’t store instance state)
- You need multiple inheritance (Ruby classes still inherit from one superclass)
- The behavior is specific to one class (keep it private)
- You find yourself overriding methods with conditionals
Module methods: the three types
Ruby modules can define three kinds of methods:
- Instance methods: Added via
include, available on class instances - Class methods: Added via
extendor defined asdef self.method_name - Module methods: Called directly on the module with
ModuleName.method_name
module Helper
# Instance method - available when included
def helper_method
"instance"
end
# Class method - available when extended
module ClassMethods
def class_method
"class"
end
end
# Module method - called as Helper.module_method
def self.module_method
"module"
end
def self.included(base)
base.extend(ClassMethods)
end
end
forward-link
If you want to see how Ruby can inspect and change objects at runtime, continue with Ruby Introspection and Reflection. That tutorial shows how modules fit into the broader metaprogramming story, especially when you start asking classes what they know about themselves.
This is also a good point to revisit the examples above and notice how often the module name appears in the code. The repetition is intentional: a good namespace should make the shape of the code obvious at a glance.
You can also compare these patterns with Ruby class_eval and instance_eval, which shows another way Ruby changes context at runtime without abandoning normal class and module structure.
Summary
Modules in Ruby provide two powerful features:
- Namespaces organize related classes and constants
- Mixins share behavior through
include(instance methods) andextend(class methods)
Use modules to keep your code modular and DRY. But remember: composition (objects containing other objects) is sometimes clearer than inheritance or mixins. When used appropriately, modules are one of Ruby’s most powerful features for code organization and reuse.
When you choose between include, extend, and prepend, think about the shape of the behavior first. If the code should act like a capability available on every instance, include is usually the right choice. If the code should configure the class itself, extend is the better fit. If you need to intercept a method before the original version runs, prepend is worth considering.
Another helpful way to think about modules is as reusable slices of behavior rather than miniature classes. A class usually represents one thing with state, while a module often represents one capability that can be shared by several things. That distinction keeps your design simpler because you can choose the lightest tool that fits the job instead of forcing every shared idea into inheritance.
That same pattern shows up in real code whenever several classes need the same helper logic but not the same data. Logging, formatting, authorization checks, and small utility methods are all natural module candidates. If the code needs its own lifecycle or persistent state, a plain object is usually a better fit.
The main design question is simple: does this behavior belong to a family of types, or does it belong to one specific object? If it is shared, a module keeps the code tidy without adding another class to your hierarchy. If it is unique, keep the code where the object lives and avoid forcing the module pattern where it does not belong.
See also
- Ruby introspection and reflection — how modules appear in the ancestor chain
- Ruby class_eval and instance_eval — another way to change context at runtime
- Ruby classes and objects — foundational understanding of Ruby’s object model