Mixins with include
Ruby doesn’t support multiple inheritance — a class can only have one superclass — but it gives you something arguably better: mixins. A mixin is a module full of methods that you fold directly into a class with the include keyword, instantly giving every instance of that class those methods. This is how Ruby lets you share behavior across unrelated classes (making something Comparable, Enumerable, or your own custom Greetable) without forcing them into a rigid inheritance tree.
Mixins are one of the most idiomatic patterns in Ruby. Understanding exactly how include works — where the module’s methods actually go, and in what order Ruby looks them up — is essential to writing (and debugging) real Ruby code.
Overview / How it works
A module is a namespace and a bag of methods. Unlike a class, a module cannot be instantiated (you can never write MyModule.new) and it cannot be subclassed. What it can do is hand its methods over to a class, and that’s exactly what include does: when a class includes a module, Ruby doesn’t copy the module’s methods into the class. Instead, it inserts the module itself into the class’s ancestor chain, right above the class.
Every Ruby object has a class, and every class has an ancestor chain — the ordered list of places Ruby looks when you call a method, stopping at the first match. You can inspect it yourself by calling .ancestors on any class. A plain class with no mixins looks like [MyClass, Object, Kernel, BasicObject]. Once you include a module, it slots in between the class and its superclass: [MyClass, MyModule, Object, Kernel, BasicObject]. That’s why an included module’s methods behave exactly like instance methods of the class — because as far as method lookup is concerned, they might as well be.
This matters for two reasons. First, it explains why methods defined directly on the class win over methods from an included module — the class itself is searched before any of its mixins. Second, it explains why the order you include modules in matters: each new include is inserted immediately above the class, which means the most recently included module is checked first, ahead of earlier ones. We’ll see both of these play out in the examples below.
Syntax
module ModuleName
def instance_method_name
"method body"
end
end
class ClassName
include ModuleName
end
module ModuleName— defines the module. By convention, module names areCamelCase, often named as adjectives (Comparable,Enumerable) or with an-ablesuffix (Greetable,Describable).- methods inside the module — defined exactly like normal instance methods; when mixed in, they run with
selfset to the including instance, so they can freely call other methods and read instance variables defined by the class. include ModuleName— called inside a class body. It inserts the module into that class’s ancestor chain, making its methods available as instance methods on every object of the class.
Examples
Example 1: A basic mixin
module Greetable
def greet
"Hello, my name is #{name}"
end
end
class Person
include Greetable
attr_reader :name
def initialize(name)
@name = name
end
end
person = Person.new("Ada")
puts person.greet
Output:
Hello, my name is Ada
The Greetable module has no idea what class will eventually include it. It just assumes that whatever includes it will respond to name — and Person supplies that via attr_reader. This is the core mixin contract: a module provides behavior, the including class provides the data that behavior needs.
Example 2: Comparable — the standard library’s flagship mixin
class Box
include Comparable
attr_reader :volume
def initialize(volume)
@volume = volume
end
def <=>(other)
volume <=> other.volume
end
end
a = Box.new(10)
b = Box.new(20)
puts a < b
puts [b, a].sort.map(&:volume).inspect
puts a.clamp(Box.new(15), Box.new(25)).volume
Output:
true
[10, 20]
15
This is the single most useful built-in mixin in Ruby. Comparable gives you <, >, <=, >=, ==, between?, clamp, and works transparently with Array#sort — all for free, as long as you implement one method: <=> (the “spaceship operator”), which returns -1, 0, or 1. a < b works because Comparable#< calls (a <=> b) < 0 under the hood. This is mixin design at its best: implement one small method, get a whole family of behavior.
Example 3: Include order and the ancestor chain
module Flyable
def move
"flies"
end
end
module Swimmable
def move
"swims"
end
end
class Duck
include Flyable
include Swimmable
end
duck = Duck.new
puts duck.move
puts Duck.ancestors.inspect
Output:
swims
[Duck, Swimmable, Flyable, Object, Kernel, BasicObject]
Both modules define move, but only one wins. Because Swimmable was included last, Ruby inserted it closer to Duck in the ancestor chain than Flyable, so it’s found first during method lookup. Printing Duck.ancestors makes the search order visible directly — this is the exact list Ruby walks, left to right, the moment you call duck.move.
Example 4: Adding class methods alongside instance methods
module Describable
def self.included(base)
base.extend(ClassMethods)
end
def description
"A #{self.class}"
end
module ClassMethods
def kind
"Describable thing"
end
end
end
class Widget
include Describable
end
puts Widget.new.description
puts Widget.kind
Output:
A Widget
Describable thing
self.included is a hook Ruby calls automatically the moment a module is included somewhere, passing in the including class as base. Here it’s used to also extend the class with a nested ClassMethods module, so include Describable grants both instance methods (description) and class-level methods (kind) in one step. This is the pattern behind many real-world Ruby libraries that add both per-object and per-class behavior with a single include.
How it works step by step
When you write duck.move, Ruby doesn’t scan the class body’s source code — it walks a precomputed list, in this order:
- 1. The object’s singleton class — methods defined on that one specific object only (via
def duck.move), if any. - 2. The object’s class itself — methods defined directly in
class Duck ... end. This is why a method the class defines always beats one from an included module. - 3. Modules included in that class, most recently included first — this is where
SwimmableandFlyablelive, and whySwimmable(included second) is checked beforeFlyable. - 4. The superclass, then its included modules, and so on up the chain, ending at
Object, thenKernel(itself a module mixed intoObject), thenBasicObject.
Ruby stops at the very first match. If nothing matches all the way up to BasicObject, it calls method_missing (which by default raises NoMethodError). super works by continuing this exact same search from the point right after the method that called it — which is why calling super from a class method can jump into an included module’s method of the same name, as in Example 4’s initialize discussion below.
Common Mistakes
Mistake 1: Forgetting that a class’s own method always wins over the module’s
People often assume that including a module automatically hooks its initialize into the class’s own initialize, the way a superclass’s initialize does. It doesn’t — not unless you call super yourself.
module Timestamped
def initialize(*args)
@created_at = "2024-01-01"
super
end
def created_at
@created_at
end
end
class Article
include Timestamped
def initialize(title)
@title = title
end
attr_reader :title
end
article = Article.new("Ruby Mixins")
puts article.created_at.inspect
Output:
nil
Because Article defines its own initialize, and the class itself is searched before its included modules, Timestamped#initialize is never reached at all — @created_at is simply never set. The fix is to explicitly call super from the class’s initialize, continuing the lookup into the module:
module Timestamped
def initialize(*args)
@created_at = "2024-01-01"
super
end
def created_at
@created_at
end
end
class Article
include Timestamped
def initialize(title)
@title = title
super()
end
attr_reader :title
end
article = Article.new("Ruby Mixins")
puts article.created_at.inspect
Output:
"2024-01-01"
Mistake 2: Confusing include with extend
include mixes a module’s methods in as instance methods. It does not add them as class methods, which is a common assumption for anyone coming from languages with static/class-level mixins.
module Describable
def description
"A #{self}"
end
end
class Widget
include Describable
end
puts Widget.description
Output (raises an error):
NoMethodError: undefined method 'description' for Widget:Class
description was mixed in as an instance method, so only instances of Widget have it — Widget the class object does not. To add methods at the class level, use extend instead, which mixes the module into the object’s singleton class rather than its instance method table. Since a class is itself an object, extending a module onto a class makes its methods callable directly on that class:
module Describable
def description
"A #{self}"
end
end
class Widget
extend Describable
end
puts Widget.description
Output:
A Widget
Best Practices
- Name mixins as adjectives or capabilities (
Comparable,Enumerable,Greetable) rather than nouns, to signal “this class can do X” rather than “this class is X”. - Keep mixins focused on one capability. A module that mixes unrelated concerns (logging and serialization and validation) is harder to reason about and to reuse.
- Document what a module expects from its host class (e.g. “the including class must implement
<=>“) since Ruby won’t enforce it — you’ll only find out at call time viaNoMethodError. - Reach for
extend, notinclude, when you want to add class-level (not instance-level) methods; use theself.included(base)hook when a singleincludeshould provide both. - Remember that method lookup checks the class itself before any included module, and checks later-included modules before earlier ones — use this to reason about surprising override behavior instead of guessing.
- If a module overrides a method like
initializethat the including class also defines, callsuperdeliberately in the class so both run, rather than relying on it happening automatically. - Prefer mixins over deep inheritance hierarchies when you’re sharing behavior across classes that aren’t naturally “is-a” related (a
Duckand anAirplanecan both beFlyablewithout sharing a common superclass).
Practice Exercises
- Exercise 1: Write a module
Nameablewith a methodfull_namethat interpolatesfirst_nameandlast_name. Include it in aCustomerclass that supplies those viaattr_reader, and print the full name for a sample customer. - Exercise 2: Write two modules,
WalkableandRollable, each defining atravelmethod that returns a different string. Include both in a class calledRobot, in each order, and predict (then verify) whichtravelwins each time by printingRobot.ancestors. - Exercise 3: Give a
Temperatureclass an@degreesvalue and implement<=>so it caninclude Comparable. Create an array of severalTemperatureobjects and call.sortand.minon it — expected result: the coldest temperature is returned by.min, and.sortreturns them coldest to hottest.
Summary
- A mixin is a
modulewhose methods are folded into a class viainclude, giving instances of that class those methods without using inheritance. includeworks by inserting the module into the class’s ancestor chain, directly above the class itself — it does not copy methods, it changes where Ruby looks.- Method lookup order is: singleton class, then the class itself, then its included modules (most recently included first), then the superclass and its modules, and so on up to
BasicObject. - A method defined directly on the class always wins over a same-named method from an included module — use
superto reach the module’s version deliberately. includeadds instance methods;extendadds methods at the object/class level. Use theself.included(base)hook to provide both from one module.ComparableandEnumerableare the standard library’s flagship mixins — implement one core method (<=>oreach) and get a whole family of behavior for free.
