rubyguides

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:

  1. Instance methods: Added via include, available on class instances
  2. Class methods: Added via extend or defined as def self.method_name
  3. 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

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:

  1. Namespaces organize related classes and constants
  2. Mixins share behavior through include (instance methods) and extend (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