Modules: shared behaviour, and names for things
A module cannot be built, and that is the point: it holds methods to be mixed into classes, and it gives namespaces so two Items can live in one program.
Behaviour that belongs to more than one class
A module is a container for methods, declared like a class but never built with new. Its first job is sharing: methods written inside the module can be mixed into any class with include, and from that moment every object of the class answers them. Where inheritance says is a, a mixin says can do this — and a class may include as many modules as it likes, which is how Ruby offers the good part of multiple inheritance without the ambiguity.
include adds the methods as instance methods. extend adds them to one object on its own, and a module used with extend inside a class adds the methods to the class itself, which is how many libraries define helpers like attr_reader — modules all the way down.
Enumerable: define one method, receive a dozen
The standard library is full of mixins, and the most instructive is Enumerable. A class that includes it and defines just one method — each, yielding its items in turn — suddenly has map, select, sort_by, min_by, group_by, include?, sum and dozens more. All of them are written in terms of each, and none of them are written by you.
That is worth pausing on, because it is the clearest statement of Ruby's design: the language gives you small pieces that compose, and a class of twenty lines can behave like a collection. A Playlist that knows how to go one song at a time knows how to be sorted, searched and summarised without any of those words appearing in its source.
Namespaces, and constants next door
The second job of a module is a surname. module Shop; class Item; end; end puts a class inside a namespace, reached from outside as Shop::Item. Two libraries may both define an Item as long as one is Shop::Item and the other Warehouse::Item — and as programs grow, this is how a collection of related classes stays greppable and its names stay short.
Constants declared in a module belong to it and are reached the same way, Shop::TAX_RATE, which is a tidy place for the numbers that belong to a corner of the program. Nesting modules is ordinary — Shop::Payments::Gateway — and each colon pair is one level deeper.
The ancestor chain
When you include a module, Ruby inserts it into the class's ancestor chain, between the class and its superclass. Method lookup walks that chain, which means a method defined in the class beats one from the module, and the module beats the superclass. prepend puts the module in front of the class instead, which is how a wrapper can run before the original.
extend is the same machinery pointed at a single object: it adds the module's methods to that object's own singleton class. A module used to group class-level helpers is usually extended, not included, because the methods are meant to be called on the class. Namespacing is separate again — a module used as a container is never mixed in at all.
Include is a lookup rule
include does not copy methods into the class. It inserts the module into the ancestor chain, and method lookup walks that chain when a message arrives. That is why a method defined in the class overrides the module’s version, and why a later include can override an earlier one.
The chain also explains super: it continues the search from the next place in the chain, not necessarily the parent class. A class method can call super and reach the module it included, which is how a class wraps a mixin’s behaviour without copying it.
A module's instance variables belong to whichever object includes it, so two classes that include the same module do not share state through it. Including a module that calls a method the class does not define fails only when that method is called, not when the module is included — the error arrives later than you would like. And two mixins that define the same method are resolved by the order of inclusion: the last one included wins, which is easy to forget when the includes are pages apart.
Mixed in, and named
module Greeting
def greet
"Hello from a #{self.class}"
end
end
module Loud
def shout(text)
"#{text.upcase}!"
end
end
class Robot
include Greeting
include Loud
end
robot = Robot.new
puts robot.greet
puts robot.shout('beep')
# Include Enumerable, define each, and the family arrives.
class Playlist
include Enumerable
def initialize(songs)
@songs = songs
end
def each
@songs.each { |song| yield song }
end
def longest
max_by { |song| song.length }
end
end
playlist = Playlist.new(%w[Intro Verse Chorus Bridge])
p playlist.map { |song| song.upcase }
p playlist.select { |song| song.length > 5 }
p playlist.sort_by { |song| song.length }
puts playlist.longest
puts playlist.include?('Verse')
# A namespace is a surname.
module Shop
TAX_RATE = 0.2
class Item
def initialize(name)
@name = name
end
def to_s
"item: #{@name}"
end
end
end
puts Shop::Item.new('desk')
puts Shop::TAX_RATEA reading list that behaves like a collection
Run it and notice that the reading list was never given map or select — they arrived from Enumerable. Then add a module method of your own, mix it in, and call it on the list.
The test for whether something should be a mixin: can it describe several unrelated classes without claiming to be their ancestor? A thing that can be sorted, saved or measured is a mixin; a thing that is a kind of something is a class.
Try these next
- Write a
Describablemodule with adescribemethod, include it in two unrelated classes, and call it on both. - Use
extendto add a helper to one object only, and show that a second object does not have it. - Build
Workshop::ToolandGarden::Tool, and print both class names to prove the namespaces.
One price rule, two kinds of thing
Run it and compare the two totals. Then add a third class that includes the same module, and a namespace with a constant of its own.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What happens if you call new on a module?
