Mixins with include

Published by Programming Line in Ruby. Last updated .

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 are CamelCase, often named as adjectives (Comparable, Enumerable) or with an -able suffix (Greetable, Describable).
  • methods inside the module — defined exactly like normal instance methods; when mixed in, they run with self set 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 Swimmable and Flyable live, and why Swimmable (included second) is checked before Flyable.
  • 4. The superclass, then its included modules, and so on up the chain, ending at Object, then Kernel (itself a module mixed into Object), then BasicObject.

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 via NoMethodError.
  • Reach for extend, not include, when you want to add class-level (not instance-level) methods; use the self.included(base) hook when a single include should 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 initialize that the including class also defines, call super deliberately 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 Duck and an Airplane can both be Flyable without sharing a common superclass).

Practice Exercises

  • Exercise 1: Write a module Nameable with a method full_name that interpolates first_name and last_name. Include it in a Customer class that supplies those via attr_reader, and print the full name for a sample customer.
  • Exercise 2: Write two modules, Walkable and Rollable, each defining a travel method that returns a different string. Include both in a class called Robot, in each order, and predict (then verify) which travel wins each time by printing Robot.ancestors.
  • Exercise 3: Give a Temperature class an @degrees value and implement <=> so it can include Comparable. Create an array of several Temperature objects and call .sort and .min on it — expected result: the coldest temperature is returned by .min, and .sort returns them coldest to hottest.

Summary

  • A mixin is a module whose methods are folded into a class via include, giving instances of that class those methods without using inheritance.
  • include works 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 super to reach the module’s version deliberately.
  • include adds instance methods; extend adds methods at the object/class level. Use the self.included(base) hook to provide both from one module.
  • Comparable and Enumerable are the standard library’s flagship mixins — implement one core method (<=> or each) and get a whole family of behavior for free.