Modules & Mixins
A Ruby module is a namespace + a bag of methods. Two main uses: include to mix instance methods into a class; extend to add class methods. Modules are how Ruby does multiple inheritance.
include, extend, namespace, comparable
EXAMPLE
# 1) Module as a namespace
module Geometry
PI = 3.14159
def self.area_of_circle(r)
PI * r * r
end
class Point
attr_reader :x, :y
def initialize(x, y) = (@x, @y = x, y)
end
end
Geometry::PI # 3.14159
Geometry.area_of_circle(5)
Geometry::Point.new(1, 2)
# 2) Module as a mixin (include) — adds instance methods
module Greetable
def greet
"Hi, I'm #{name}"
end
end
class User
attr_reader :name
include Greetable
def initialize(name) = @name = name
end
User.new('Ada').greet # "Hi, I'm Ada"
# 3) extend — adds CLASS methods
module Findable
def find(id) = all.find { |x| x.id == id }
end
class User
extend Findable
def self.all = @all ||= []
end
User.find(1)
# 4) include vs extend (the one rule)
# include : instance methods (User#greet)
# extend : class methods (User.find)
# Self-extending pattern — add both at once
module MyMixin
def instance_method = 'instance'
module ClassMethods
def class_method = 'class'
end
def self.included(base)
base.extend(ClassMethods)
end
end
class Foo
include MyMixin
end
Foo.new.instance_method # 'instance'
Foo.class_method # 'class'
# 5) Comparable — implement <=> and get >, <, ==, between?
class Version
include Comparable
attr_reader :parts
def initialize(s)
@parts = s.split('.').map(&:to_i)
end
def <=>(other) = parts <=> other.parts
end
v1 = Version.new('1.2.3')
v2 = Version.new('1.10.0')
v1 < v2 # true
[v1, v2, Version.new('0.9')].sort
# 6) Enumerable — implement each and get map, select, reduce, find, etc.
class Range2
include Enumerable
def initialize(min, max) = (@min, @max = min, max)
def each
(@min..@max).each { |n| yield n }
end
end
r = Range2.new(1, 5)
r.to_a # [1, 2, 3, 4, 5]
r.map { |n| n * n } # [1, 4, 9, 16, 25]
r.select(&:even?) # [2, 4]
r.reduce(:+) # 15
r.find { |n| n > 3 } # 4
# 7) prepend — wrap methods (decorator pattern)
module Logged
def call(*args)
puts "calling with #{args}"
result = super
puts "returned #{result}"
result
end
end
class Service
prepend Logged
def call(name) = "hello #{name}"
end
Service.new.call('Ada')
# calling with ["Ada"]
# returned hello Ada
Service.ancestors # [Logged, Service, Object, ...]
# include puts the module AFTER the class in the lookup chain.
# prepend puts it BEFORE — `super` in the module calls the class's method.
# 8) Module functions — can be called as methods OR on the module itself
module MathUtils
module_function
def square(x) = x * x
def cube(x) = x * x * x
end
MathUtils.square(4) # 16 (class-style)
include MathUtils
square(4) # 16 (instance-style)
# 9) Constants in modules
module Config
DEFAULT_TIMEOUT = 30
DEFAULT_RETRIES = 3
end
Config::DEFAULT_TIMEOUT
# 10) Common standard-library modules
# Enumerable — implement `each`, get a fluent collection API
# Comparable — implement `<=>`, get comparison operators
# Math — math.sin, Math.sqrt, etc.
# Kernel — methods available everywhere (puts, raise, lambda)
# ObjectSpace — introspection (rarely used in app code)
# Singleton — restrict a class to one instance
# 11) Namespacing nested modules / classes
module MyApp
module Models
class User; end
class Post; end
end
module Services
class Authenticator; end
end
end
MyApp::Models::User
MyApp::Services::Authenticator
# Rails-style: file paths mirror module names (autoload-friendly)
# app/models/my_app/models/user.rb
# app/services/my_app/services/authenticator.rb
# 12) refinements — scoped monkey-patching (Ruby 2.0+)
module StringPlus
refine String do
def shout = upcase + '!'
end
end
class Foo
using StringPlus
def yell(s) = s.shout
end
Foo.new.yell('hi') # 'HI!'
# 'hi'.shout # NoMethodError — refinement only inside Foo
# Use refinements when you want to add behaviour without polluting global namespace.
# 13) Singleton pattern
require 'singleton'
class Logger
include Singleton
def log(msg) = puts "[log] #{msg}"
end
Logger.instance.log('hi')
# Logger.new # NoMethodError — Singleton blocks .new
# 14) Common patterns
# a) Stateless utility module
module TextUtils
module_function
def slugify(s) = s.downcase.gsub(/[^a-z0-9]+/, '-').sub(/^-|-$/, '')
def truncate(s, max) = s.length <= max ? s : s[0...max] + '...'
end
# b) Behavioural mixin
module Auditable
def audit(action, payload = {})
Audit.log(class: self.class.name, action: action, payload: payload)
end
end
class Order
include Auditable
end
# c) Concerns (Rails)
module Sluggable
extend ActiveSupport::Concern
included do
before_save :generate_slug
end
def generate_slug = self.slug = title.parameterize
class_methods do
def find_by_slug(s) = find_by(slug: s)
end
end
class Post < ApplicationRecord
include Sluggable
end
# 15) Best practices
# • Use modules to share behaviour across unrelated classes
# • Prefer composition (delegation, modules) over inheritance
# • Keep modules focused — one responsibility each (Auditable, Cacheable, Loggable)
# • Use Comparable + Enumerable for value objects
# • Don't nest deeply — 2 levels is usually enough
# • Module functions for stateless helpers (slugify, parse_x)
# 16) Common bugs
# • Forgetting that include adds instance methods only (extend for class methods)
# • Using prepend without `super` → infinite recursion
# • Refinements not being `using`-imported in the right scope
# • Confusing constant lookup — Module nesting matters
# • Mutable shared state in module-level @@variables (avoid)
Why it matters
Modules are Ruby’s answer to multiple inheritance: small, focused mixins (Comparable, Enumerable, Auditable) composed into classes via include/extend/prepend. Reach for them before adding another inheritance level.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
module Greetable
def hello = "hi, #{name}"
end
class User
include Greetable
attr_reader :name
def initialize(name) = @name = name
end
Try it Yourself »
Exercise
Mix a module into a class.
class User
Greetable
end
Seven letters.
Discussion
Loading…