Modules & Mixins

Ruby Fundamentals — Modules & Mixins
Ruby Fundamentals
Course 1 · Chapter 7 · Modules & Mixins

🧩 Modules & Mixins

Ruby classes support only single inheritance — a class has exactly one superclass, full stop. Chapter 5 mentioned, without fully explaining, that 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

class Dog < Animal # fine -- one superclass end # There is no syntax for this in Ruby at all: # class Dog < Animal, Pet

Inheriting From Multiple Sources: PHP vs Python vs Ruby

PHPPythonRuby
True multiple inheritance?No — single class inheritanceYes — class C(A, B): is validNo — single class inheritance
Shared-behavior workaroundTraits (use TraitName;)Multiple inheritance directly, or mixins by conventionModules, 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:

module Greetable def greet "Hello, I'm #{name}!" # assumes the including class defines a name method end end

➕ include — Mix In Instance Methods

module Greetable def greet "Hello, I'm #{name}!" end end class Person include Greetable attr_accessor :name def initialize(name) @name = name end end class Robot include Greetable # completely unrelated to Person -- no shared superclass attr_accessor :name def initialize(name) @name = name end end Person.new("Philip").greet # => "Hello, I'm Philip!" Robot.new("R2D2").greet # => "Hello, I'm R2D2!"

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:

module Identifiable def class_id "ID: #{name}" # name here refers to the class's own name end end class Product extend Identifiable end Product.class_id # => "ID: Product" -- called on the class, not an instance

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 itselfProduct.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:

class Array include Enumerable # (simplified -- this is conceptually what's happening) end class Hash include Enumerable # same module, completely different class end

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.

→ Solution

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.

→ Solution

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.

→ Solution

💎 Why Ruby Avoided True Multiple Inheritance

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.