Modules & Mixins
🧩 Modules & Mixins
Array and Hash both share the same Enumerable methods despite having no inheritance relationship to each other. This chapter explains how: modules, and the include/extend mechanism Ruby uses instead of true multiple inheritance.
🧬 One Superclass, No Exceptions
Inheriting From Multiple Sources: PHP vs Python vs Ruby
| PHP | Python | Ruby | |
|---|---|---|---|
| True multiple inheritance? | No — single class inheritance | Yes — class C(A, B): is valid | No — single class inheritance |
| Shared-behavior workaround | Traits (use TraitName;) | Multiple inheritance directly, or mixins by convention | Modules, mixed in with include/extend |
This table has a genuine surprise in it: Python actually allows true multiple inheritance directly (class C(A, B):), something neither PHP nor Ruby permits. PHP's answer is traits. Ruby's answer is modules — and while the underlying goal (share behavior across otherwise-unrelated classes) is the same as PHP traits, Ruby's mechanism looks different enough to be worth learning on its own terms.
📦 What Is a Module?
A module is defined like a class, but it can never be instantiated — there's no Module.new. It's purely a named container for methods (and constants) meant to be shared:
➕ include — Mix In Instance Methods
include mixes a module's methods into a class as instance methods — every instance of Person and Robot now has greet available, even though the two classes share no common ancestor besides Object. This is the direct Ruby equivalent of PHP's use TraitName; inside a class body.
🔌 extend — Mix In Class Methods
Where include adds instance methods, extend adds the module's methods as class methods instead — an alternative to writing def self.method_name directly, from Chapter 6:
include → instance methods
Methods become available on objects created from the class — Person.new(...).greet. This is by far the more common of the two in everyday Ruby code.
extend → class methods
Methods become available on the class itself — Product.class_id, no instance involved. Reach for this specifically when the shared behavior is about the class as a whole, not about individual instances of it.
🔮 The Payoff: Enumerable, Revealed
Chapter 5's map, select, and reduce now make complete sense as a mechanism, not just a fact to memorize. Ruby's actual standard library source is, in simplified spirit, doing exactly this:
Array and Hash share no inheritance relationship whatsoever — one isn't a subclass of the other. They both get map/select/reduce for the exact reason Person and Robot both got greet above: the same module, mixed into two otherwise-unrelated classes.
💻 Coding Challenges
Challenge 1: A Loggable Module
Write a module Loggable with a method log(message) that prints "[LOG] #{message}". Create a class Order that includes Loggable, instantiate it, and call .log("Order created") on the instance.
Goal: Write your first module and mix it into a class with include.
Challenge 2: Shared Behavior, No Common Ancestor
Reusing the Loggable module from Challenge 1, create a second, completely unrelated class Invoice (no shared superclass with Order) that also includes Loggable. Call .log on an instance of each class to prove both work identically.
Goal: See directly that include shares behavior without requiring any inheritance relationship between the classes.
Challenge 3: extend for a Class-Level Method
Write a module Describable with a method describe that returns "This is the #{name} class". extend it into a class of your choice and call .describe directly on the class (not an instance).
Goal: Feel the difference between include (instance methods) and extend (class methods) firsthand.
True multiple inheritance (Python's approach) runs into the classic "diamond problem" — if two parent classes both define a method with the same name, which one wins? Python resolves this with a defined, sometimes-surprising resolution order (MRO). Ruby's designers sidestepped the ambiguity entirely: a class still has exactly one superclass, and mixed-in modules are inserted into a single, well-defined ancestor chain (inspectable with SomeClass.ancestors) — so there's always one unambiguous answer for which greet method actually runs.
🎯 What's Next
Next chapter: Blocks, Procs & Lambdas — the final chapter of Ruby Fundamentals, taking the block/yield mechanics from Chapter 4 the rest of the way and turning blocks into objects you can store, pass around, and call explicitly.