Modules and mixins
A module in Ruby is a namespace and a mixin. As a namespace, it groups related constants, classes, and module-level methods. As a mixin, it provides instance methods that may be included into classes — admitting multiple-inheritance-style code reuse without the substantive complexity. Three principal mechanisms attach a module to a class: include (mixes in instance methods), extend (mixes in singleton methods — methods on the receiver), and prepend (Ruby 2.0+; inserts the module before the class in the lookup chain). The conventional standard-library mixins — Comparable, Enumerable, Forwardable — admit substantial functionality from a single method definition. The combination — modules as namespaces and mixins, the three attachment forms, the substantial standard-library mixins — is the substance of Ruby’s mixin system.
Module declarations
The module keyword introduces a module:
module Geometry
PI = 3.14159
def self.degrees_to_radians(d) # module-level method
d * PI / 180
end
class Circle # nested class
def initialize(radius)
@radius = radius
end
end
end
# Access:
Geometry::PI
Geometry.degrees_to_radians(90)
Geometry::Circle.new(5)
A module:
- May contain constants, methods, and nested classes/modules.
- May not be instantiated directly.
- May be a namespace, a mixin, or both.
Module as namespace
For grouping related constants and classes:
module Network
DEFAULT_PORT = 8080
class Connection
def initialize(host, port: DEFAULT_PORT)
@host = host
@port = port
end
end
class Server
def listen(port: DEFAULT_PORT)
# ...
end
end
module Protocols
HTTP = "http"
HTTPS = "https"
end
end
Network::Connection.new("example.com")
Network::Protocols::HTTP
The conventional uses are library packages, gem internal organisation, and avoiding name collisions.
Module-level methods
Two principal forms:
module Math
def self.square(n) # def self.foo
n * n
end
def self.cube(n)
n ** 3
end
end
Math.square(5) # 25
# Or via module_function:
module Helper
module_function
def greet(name)
"Hello, #{name}"
end
end
Helper.greet("Alice")
Helper.greet("Bob")
The module_function admits using methods both as module-level methods and as instance methods (when the module is included).
include — mixin instance methods
The include mixes in instance methods:
module Greeting
def hello
"Hello, I am #{name}"
end
end
class Person
include Greeting
attr_reader :name
def initialize(name)
@name = name
end
end
Person.new("Alice").hello # "Hello, I am Alice"
The mechanism admits substantial code reuse:
module Logging
def log(message)
puts "[#{Time.now}] #{message}"
end
end
class Service
include Logging
# log is now an instance method on Service
end
Service.new.log("started")
include adds the module to the ancestor chain — between the class and its superclass:
class A
def foo; "from A"; end
end
module M
def foo; "from M"; end
end
class B < A
include M
end
B.new.foo # "from M"
B.ancestors # [B, M, A, Object, Kernel, BasicObject]
The lookup order: B → M → A → Object → .... The mixin shadows the parent class’s method.
extend — mixin singleton methods
The extend mixes in methods on the receiver (typically a class):
module Helpers
def helper
"helper called"
end
end
class Foo
extend Helpers
end
Foo.helper # "helper called" (class method)
# extend can also add to a single instance:
foo = "hello"
foo.extend(Helpers)
foo.helper # "helper called"
The principal use cases:
- Adding class methods — modules’ methods become class methods of the receiver.
- Singleton-method extension — adding methods to a single object.
A common pattern: include and extend simultaneously:
module Greetable
module ClassMethods
def all_greetings
["hello", "hi", "hey"]
end
end
module InstanceMethods
def greet
"Hello"
end
end
def self.included(base)
base.extend(ClassMethods)
base.include(InstanceMethods)
end
end
class Person
include Greetable
end
Person.all_greetings # class method
Person.new.greet # instance method
The self.included hook admits running code when the module is included; the pattern is conventional in Rails-style “concerns”.
prepend — mixin before the class
The prepend (Ruby 2.0+) inserts the module before the class in the lookup chain:
module Logging
def hello
puts "logging: about to greet"
super # calls the prepended class's hello
end
end
class Person
prepend Logging
def hello
puts "Hello!"
end
end
Person.new.hello
# logging: about to greet
# Hello!
The prepend admits wrapping methods — particularly useful for cross-cutting concerns like logging, caching, or instrumentation.
The ancestor chain with prepend:
class B
prepend M
end
B.ancestors # [M, B, Object, ...]
M is before B — B.new.method looks up M first.
Mixing modules into modules
A module can include other modules:
module Logging
def log(msg); puts msg; end
end
module Tracing
include Logging # Tracing now has log
def trace(msg); log("trace: #{msg}"); end
end
class Service
include Tracing # gets log AND trace
end
The mechanism admits substantial composition.
Comparable
The standard Comparable mixin derives <, <=, >, >=, ==, between?, clamp from <=>:
class Distance
include Comparable
attr_reader :meters
def initialize(meters)
@meters = meters
end
def <=>(other)
meters <=> other.meters
end
end
a = Distance.new(100)
b = Distance.new(200)
a < b # true
a.between?(Distance.new(50), Distance.new(150)) # true
[a, b].min # a
The <=> (spaceship) returns -1, 0, 1, or nil. Defining it admits the substantial comparison surface.
Enumerable
The Enumerable mixin provides substantial iteration methods (map, select, reject, reduce, sort, find, count, to_a, etc.) — derived from a single each method:
class TodoList
include Enumerable
def initialize
@items = []
end
def add(item)
@items << item
self
end
def each(&block)
@items.each(&block)
end
end
list = TodoList.new
list.add("buy milk")
list.add("write code")
list.map { |i| i.upcase } # via Enumerable
list.select { |i| i.include?("milk") }
list.first
list.count
list.sort
list.to_a
Treated in Enumerable.
Forwardable
The Forwardable admits delegation:
require "forwardable"
class TodoList
extend Forwardable
def_delegators :@items, :each, :size, :empty?
def initialize
@items = []
end
def add(item)
@items << item
end
end
list = TodoList.new
list.add("buy milk")
list.size # 1 (delegated to @items)
list.each { |i| puts i } # delegated
The pattern admits substantial conciseness for “this object delegates these methods to a contained object”.
Singleton
The Singleton mixin admits the singleton pattern:
require "singleton"
class Logger
include Singleton
def log(message)
puts message
end
end
Logger.instance.log("hello")
# Logger.new # NoMethodError (private)
The conventional discipline avoids singletons for testability; explicit dependency injection is conventionally preferred.
Custom mixins
The pattern is conventional for shared behaviour:
module Persistable
def save
File.write(filename, serialize)
end
def filename
"#{self.class.name.downcase}_#{id}.json"
end
def serialize
raise NotImplementedError, "subclass must implement"
end
end
class User
include Persistable
attr_reader :id
def initialize(id)
@id = id
end
def serialize
JSON.dump(id: @id)
end
end
The pattern admits substantial reuse with hook methods that subclasses override.
Constants in modules
Constants in modules admit lexical scoping:
module API
TIMEOUT = 30
class Client
def fetch(url)
with_timeout(TIMEOUT) { http_get(url) } # TIMEOUT visible
end
end
end
The constant is found via lexical lookup; the Client class doesn’t need to qualify TIMEOUT as API::TIMEOUT.
Conditional inclusion
class Service
include Logging if Rails.env.development? # admitted but rare
end
The pattern is admitted but conventionally avoided; consistent class definitions are preferred.
Common patterns
Concern-style mixin
module Trackable
def self.included(base)
base.extend(ClassMethods)
end
def track(action)
self.class.tracker.track(self, action)
end
module ClassMethods
def tracker
@tracker ||= Tracker.new
end
end
end
class User
include Trackable
end
User.tracker # class method
User.new.track("login") # instance method
The pattern is the conventional Rails “concern”.
Comparable for sortable values
class Priority
include Comparable
ORDER = { low: 0, medium: 1, high: 2 }
attr_reader :level
def initialize(level)
raise ArgumentError unless ORDER.key?(level)
@level = level
end
def <=>(other)
ORDER[level] <=> ORDER[other.level]
end
end
[:high, :low, :medium].map { |l| Priority.new(l) }.sort
Enumerable for collections
class Inventory
include Enumerable
def initialize
@items = {}
end
def add(item, quantity)
@items[item] = quantity
end
def each(&block)
@items.each(&block)
end
end
inv = Inventory.new
inv.add(:apple, 10)
inv.add(:banana, 5)
inv.count # 2
inv.select { |item, qty| qty > 7 }
inv.find { |item, _| item == :banana }
inv.to_a
Forwardable delegation
require "forwardable"
class TodoList
extend Forwardable
def_delegators :@items, :each, :size, :empty?, :first, :last
def_delegator :@items, :length, :item_count # alias
def initialize
@items = []
end
def add(item)
@items << item
self
end
end
list = TodoList.new
list.add("a").add("b")
list.size # 2
list.item_count # 2 (aliased)
Module as namespace and mixin
module HTTP
TIMEOUT = 30 # constant
class Client # nested class
def get(url)
# ...
end
end
module Helpers # nested module (mixin)
def http_get(url) # instance method when included
Client.new.get(url)
end
end
module_function
def url_encode(s) # module-level method
# ...
end
end
# Usage:
client = HTTP::Client.new # nested class
encoded = HTTP.url_encode("hello world") # module method
class Service
include HTTP::Helpers # mixin
end
Refinements (lexical mixins)
module StringEnhancements
refine String do
def shout
upcase + "!"
end
end
end
class MyClass
using StringEnhancements
def example
"hello".shout # "HELLO!"
end
end
# "hello".shout # NoMethodError outside MyClass
Refinements admit lexically-scoped extensions; conventional for library-internal tweaks without polluting global classes.
Hook methods
module Trackable
def self.included(base)
puts "#{base} included Trackable"
base.extend(ClassMethods)
end
def self.extended(base)
puts "#{base} extended Trackable"
end
module ClassMethods
def track(action)
# ...
end
end
end
The hooks (included, extended, prepended, inherited) admit running code when the module is attached.
A note on multiple inheritance
Single inheritance plus mixins covers the substance of multiple-inheritance use cases:
class Animal; end
module Walking
def walk; end
end
module Swimming
def swim; end
end
class Duck < Animal
include Walking
include Swimming
end
The mechanism admits substantial code reuse without the diamond inheritance problem.
The lookup order is well-defined: included modules are inserted into the ancestor chain in reverse-include order (the last-included module wins for conflicts):
module A; def foo; "A"; end; end
module B; def foo; "B"; end; end
class C
include A
include B
end
C.new.foo # "B" (last include wins)
C.ancestors # [C, B, A, Object, ...]
A note on the conventional discipline
The contemporary Ruby modules-and-mixins advice:
- Use modules for namespacing — group related code.
- Use modules for mixins — share behaviour.
- Use
includefor shared instance methods. - Use
extendfor shared class methods. - Use
prependfor wrapping/instrumentation. - Use
Comparable— derive comparisons from<=>. - Use
Enumerable— derive iteration fromeach. - Use
Forwardable— delegate methods to contained objects. - Use the
includedhook for “concern”-style class extensions. - Use refinements for lexically-scoped patches.
- Avoid
Singleton— explicit dependency injection is preferred. - Avoid global mixin pollution — mix into the smallest scope that works.
The combination — modules as namespaces and mixins, include/extend/prepend for attachment, the Comparable/Enumerable/Forwardable standard mixins, hook methods for substantial introspection — is the substance of Ruby’s mixin system. The discipline produces flexible, composable code without the substantive complexity of true multiple inheritance.