Vol. 5 · № 01 · Un atelier RubyA Ruby workshop

Modules, ou un seul mot pour deux outils Modules, or one word for two tools

Un module fait deux métiers : ranger (espace de noms accédé par ::) et partager (mixin injecté par include). Le verbe trahit l'intention. Le piège : mixer tout un module là où l'on voulait juste nommer une constante. A module does two jobs: organizing (a namespace reached via ::) and sharing (a mixin injected via include). The verb betrays the intention. The trap: mixing a whole module where you only wanted to name a constant.

AudienceAudience
Dev qui a vu l'objet et veut savoir d'où vient son comportement Dev who's met the object and wants to know where its behavior comes from
Format
Self-paced
ChapitresChapters
5
Date
Mar 2026 Mar 2026
≈ 18 min ●●○○ ModuleMixinNamespace
SommaireContents ·
№ 01CadrageFraming3 min

Un seul mot, module, pour deux outils différents.One word, module, for two different tools.

Le Vol 5 ouvre la question : d'où un objet tient-il son comportement ? Et la réponse de Ruby commence par le module — un mot qui recouvre deux usages qu'on confond souvent. Premier rôle : ranger. Un module est un espace de noms qui regroupe constantes et classes sous un préfixe, pour éviter les collisions (Billing::Invoice n'est pas Shipping::Invoice). Second rôle : partager. Un module mixé dans une classe par include y injecte ses méthodes, comme un héritage de capacité. Même mot, deux intentions — et tout le volume commence par les distinguer.Vol 5 opens the question: where does an object get its behavior? And Ruby's answer starts with the module — one word covering two uses we often conflate. First role: organizing. A module is a namespace grouping constants and classes under a prefix, to avoid collisions (Billing::Invoice isn't Shipping::Invoice). Second role: sharing. A module mixed into a class via include injects its methods, like an inheritance of capability. Same word, two intentions — and the whole volume begins by telling them apart.

Ranger (::) et partager (include) : deux usagesOrganize (::) and share (include): two uses
module Billing                 # 1) un ESPACE DE NOMS : il range
  TAX = 0.2
  class Invoice; end           # Billing::Invoice, pas la même qu'ailleurs
end

module Greetable               # 2) un MIXIN : il partage du comportement
  def greet = "Bonjour, #{name}"
end

Billing::TAX                   # => 0.2      on RANGE et on accède via ::
class User
  include Greetable            # on PARTAGE : greet entre dans User
  attr_reader :name
  def initialize(n) = @name = n
end
User.new("Ada").greet          # => "Bonjour, Ada"
Les deux visages d'un moduleA module's two faces
Espace de nomsNamespace
il RANGEit ORGANIZES
Billing::TAX
Billing::Invoice
MixinMixin
il PARTAGEit SHARES
include Greetable
→ greet injecté

À gauche, on accède de l'extérieur avec :: : le module reste un tiroir. À droite, on l'aspire dans une classe avec include : ses méthodes deviennent celles de la classe. Le même module, selon qu'on le nomme ou qu'on l'inclut.On the left, you access it from outside with ::: the module stays a drawer. On the right, you pull it into a class with include: its methods become the class's own. The same module, depending on whether you name it or include it.

Le réflexe du numéroThe issue's reflex

« Est-ce que je veux ranger sous un préfixe, ou partager un comportement ? » Si c'est ranger, on garde le module dehors et on y accède par ::. Si c'est partager, on l'include. Confondre les deux, c'est aspirer dans sa classe tout un module dont on ne voulait qu'une constante."Do I want to organize under a prefix, or share a behavior?" If organizing, keep the module outside and reach it via ::. If sharing, include it. Conflating the two pulls a whole module into your class when you wanted just one constant.

Un module n'est pas une classe.A module isn't a class.

On ne peut pas faire Greetable.new : un module ne s'instancie pas, et n'a pas de superclasse. C'est justement ce qui le rend mixable sans limite là où l'héritage est simple. Il reste pourtant un objet (Vol 1) — Greetable.class répond Module, et Class hérite d'ailleurs de Module. Une classe, c'est un module qui sait s'instancier.You can't do Greetable.new: a module isn't instantiable, and has no superclass. That's exactly what makes it mixable without limit where inheritance is single. It's still an object (Vol 1) — Greetable.class answers Module, and Class in fact inherits from Module. A class is a module that can instantiate.

№ 02La surpriseThe surprise4 min

Ranger laisse le module dehors ; mixer le fait entrer.Organizing keeps the module outside; mixing pulls it in.

La même définition de module se manipule de deux façons opposées. En espace de noms, le module reste un tiroir extérieur : on y accède par ::, rien n'entre dans aucune classe — c'est de l'organisation pure, qui évite les collisions de noms. En mixin, include fait l'inverse : il insère le module dans la chaîne d'ancêtres de la classe, et ses méthodes deviennent des méthodes d'instance, appelables comme si elles avaient été écrites là. Le premier usage range des noms ; le second greffe du comportement.The same module definition is handled in two opposite ways. As a namespace, the module stays an outer drawer: you reach it via ::, nothing enters any class — pure organization that avoids name collisions. As a mixin, include does the reverse: it inserts the module into the class's ancestor chain, and its methods become instance methods, callable as if written there. The first use files names; the second grafts behavior.

Le module reste un tiroir, accédé par ::The module stays a drawer, reached via ::
# Le module comme TIROIR : on range, on accède du dehors par ::
module Payments
  CURRENCY = "EUR"
  class Charge
    def initialize(amount) = @amount = amount
  end
  module Gateways          # on peut imbriquer des modules
    class Stripe; end
  end
end

Payments::CURRENCY              # => "EUR"
Payments::Charge.new(10)        # une Charge bien rangée
Payments::Gateways::Stripe.new  # :: descend la hiérarchie de noms

# aucune méthode n'est entrée nulle part : le module reste DEHORS

Payments ne fait que regrouper : ses constantes et classes vivent sous son préfixe, et :: les atteint depuis l'extérieur. Rien n'est injecté nulle part — c'est ce qui permet à deux bibliothèques d'avoir chacune leur Charge sans se marcher dessus. Le module est une adresse, pas une greffe.Payments only groups: its constants and classes live under its prefix, and :: reaches them from outside. Nothing is injected anywhere — that's what lets two libraries each have their own Charge without clashing. The module is an address, not a graft.

Un module nommé est une adresse. Un module inclus est une greffe. Le code est le même ; l'intention, non.A named module is an address. An included module is a graft. The code is the same; the intention isn't.
№ 03La mécaniqueThe mechanics5 min

include, extend, module_function : où les méthodes atterrissent.include, extend, module_function: where the methods land.

Un même module donne des méthodes à des endroits différents selon le verbe. include les pose en méthodes d'instance : toutes les instances de la classe les gagnent. extend les pose sur un seul objet — et comme une classe est un objet, extend dans un corps de classe en fait des méthodes de classe. module_function, enfin, rend les méthodes appelables directement sur le module, qui devient un porte-fonctions à la Math. Trois verbes, une question : sur quel receveur veux-tu que ces méthodes répondent ?One module gives methods to different places depending on the verb. include lays them down as instance methods: every instance of the class gains them. extend lays them on a single object — and since a class is an object, extend in a class body makes them class methods. module_function, finally, makes the methods callable directly on the module, which becomes a function-holder à la Math. Three verbs, one question: on which receiver do you want these methods to answer?

include → instances ; extend → la classe ou l'objetinclude → instances; extend → the class or the object
module Sayable
  def say = "salut de #{self}"
end

class A
  include Sayable     # → méthodes d'INSTANCE
end
A.new.say             # => "salut de #<A>"   sur les instances

class B
  extend Sayable      # → méthodes de CLASSE
end
B.say                 # => "salut de B"      sur la classe elle-même

obj = "x"
obj.extend(Sayable)   # → sur CET objet uniquement
obj.say               # => "salut de x"
module_function : le module porte-fonctionsmodule_function: the function-holder module
module Calc
  module_function       # rend les méthodes suivantes appelables sur le module

  def double(x) = x * 2
  def triple(x) = x * 3
end

Calc.double(4)          # => 8    le module se comporte comme un porte-fonctions
Calc.triple(4)          # => 12   sans avoir à l'inclure ni à l'instancier
Le même module, trois receveurs.The same module, three receivers.

include vise les instances (le cas du mixin classique). extend vise un objet précis — souvent la classe elle-même, d'où l'idiome des méthodes de classe. module_function vise le module lui-même : pratique pour un sac de fonctions utilitaires (Calc.double) sans receveur à instancier. Ramène-les toujours à la règle du Vol 1 : ces méthodes répondront sur tel objet, pas en l'air.include targets the instances (the classic mixin case). extend targets one precise object — often the class itself, hence the class-method idiom. module_function targets the module itself: handy for a bag of utility functions (Calc.double) with no receiver to instantiate. Always bring them back to the Vol 1 rule: these methods will answer on that object, not in mid-air.

Une astuce courante condense deux de ces verbes : extend self dans un module le rend à la fois mixable (include ailleurs) et appelable directement (MonModule.methode). On en reparlera au № 3 du volume ; pour l'instant, retiens la grille — include pour les instances, extend pour un objet, module_function pour le module — et tu sais déjà où chaque méthode va répondre.A common trick condenses two of these verbs: extend self in a module makes it both mixable (include elsewhere) and directly callable (MyModule.method). We'll return to it in the volume's № 3; for now, keep the grid — include for instances, extend for an object, module_function for the module — and you already know where each method will answer.

№ 04Le piègeThe trap4 min

include pour une simple constante : tout un module dans la classe.include for a single constant: a whole module in the class.

Le contre-exemple du numéro naît exactement de la confusion des deux rôles. On veut une constante rangée dans un module — Math::PI — et, par réflexe, on écrit include Math. Mais include est le verbe du mixin, pas du namespace : il insère Math dans la chaîne d'ancêtres et déverse ses vingt-six fonctions en méthodes (privées) de chaque instance. On a payé une greffe entière pour lire une constante. Le bon geste tenait dans deux caractères : :: accède au tiroir sans rien aspirer. include partage ; pour ranger, on nomme.The issue's counter-example springs precisely from conflating the two roles. You want a constant filed in a module — Math::PI — and, by reflex, you write include Math. But include is the mixin verb, not the namespace one: it inserts Math into the ancestor chain and dumps its twenty-six functions as (private) methods on every instance. You paid for a whole graft to read one constant. The right move was two characters: :: reaches the drawer without pulling anything in. include shares; to organize, you name.

include Math greffe 26 méthodes pour une constanteinclude Math grafts 26 methods for one constant
# On voulait juste PI… alors on inclut tout Math
class Circle
  include Math               # mixin : aspire TOUT le module
  def area(r) = PI * r ** 2  # certes, PI est là maintenant
end

Circle.ancestors
# => [Circle, Math, Object, ...]   ← Math coincé dans la chaîne
Circle.private_instance_methods.grep(/sqrt|sin|hypot/)
# => [:sqrt, :sin, :hypot, ...]   ← 26 méthodes injectées dans CHAQUE Circle
#    pour une seule constante : on a confondu « ranger » et « partager »
:: accède au tiroir, sans greffe:: reaches the drawer, no graft
# On accède au namespace par ::, sans rien aspirer
class Circle
  def area(r) = Math::PI * r ** 2   # :: vise la constante, point
end

Circle.ancestors
# => [Circle, Object, ...]          ← Math n'est PAS dans la chaîne
Circle.private_instance_methods.grep(/sqrt|sin/)
# => []                              ← zéro pollution

Math.sqrt(9)   # et si on veut les fonctions : on les appelle sur Math
Le contre-exemple obligatoire.The mandatory counter-example.

La règle de tri : include dès que tu veux que les instances gagnent un comportement ; :: dès que tu veux seulement lire une constante ou nommer une classe rangée là. Inclure pour une constante, c'est élargir l'interface publique de ta classe avec des méthodes que personne n'a demandées — et risquer les collisions de noms du prochain volume. Demande-toi toujours : « est-ce que je veux que mes objets sachent faire ça, ou seulement désigner ça ? »The sorting rule: include whenever you want the instances to gain a behavior; :: whenever you only want to read a constant or name a class filed there. Including for a constant widens your class's public interface with methods no one asked for — and risks the next volume's name collisions. Always ask: "do I want my objects to know how to do this, or only to refer to this?"

include n'est pas « importer » : c'est faire entrer tout un module dans tes objets. Pour juste nommer, deux-points suffisent.include isn't "import": it's pulling a whole module into your objects. To merely name, a colon-pair is enough.
№ 05Bilan & éditoWrap-up & editorial2 min

Le même mot range et partage — décide lequel avant d'écrire.The same word organizes and shares — decide which before you write.

Un module fait deux métiers. En espace de noms, il range constantes et classes sous un préfixe, et on y accède de l'extérieur par ::. En mixin, include l'insère dans une classe et ses méthodes deviennent celles des instances. Le verbe trahit l'intention : :: pour nommer, include pour greffer aux instances, extend pour un objet (souvent la classe), module_function pour un porte-fonctions. Le piège est de mixer là où l'on voulait juste ranger.A module does two jobs. As a namespace, it files constants and classes under a prefix, reached from outside via ::. As a mixin, include inserts it into a class and its methods become the instances'. The verb betrays the intention: :: to name, include to graft onto instances, extend for an object (often the class), module_function for a function-holder. The trap is mixing where you only wanted to organize.

L'intentionThe intentionL'outilThe toolOù ça atterritWhere it lands
Ranger / nommerOrganize / nameModule::CONST, Module::Classe.Module::CONST, Module::Class.Nulle part : le module reste dehors.Nowhere: the module stays outside.
Partager aux instancesShare to instancesinclude Module.include Module.Méthodes d'instance, via la chaîne d'ancêtres.Instance methods, via the ancestor chain.
Donner à un objetGive to an objectextend Module (classe = méthodes de classe).extend Module (class = class methods).Sur cet objet précis seulement.On that one object only.
Un sac de fonctionsA bag of functionsmodule_function / extend self.module_function / extend self.Appelables sur le module : Module.f.Callable on the module: Module.f.
Un module est un tiroir ou une greffe — jamais par hasard les deux. Le verbe que tu choisis dit lequel.A module is a drawer or a graft — never both by accident. The verb you choose says which.
Mot de l'éditeurFrom the editor

Ouverture du Vol 5 : d'où un objet tient-il son comportement ?Opening Vol 5: where does an object get its behavior?

Quatre volumes durant, on a envoyé des messages à des objets sans trop demander d'où venaient les méthodes qui y répondaient. Le Vol 5 ouvre cette boîte. La réponse de Ruby est singulière : un seul héritage de classe, mais des modules mixés sans limite. Là où d'autres langages empilent les interfaces ou bricolent l'héritage multiple, Ruby range et partage avec le même outil — le module — et te laisse choisir, à chaque fois, entre nommer et greffer. Ce premier numéro pose le vocabulaire ; tout le volume va creuser l'arbitrage qu'il ouvre : identité ou capacité ?For four volumes we sent messages to objects without much asking where the answering methods came from. Vol 5 opens that box. Ruby's answer is singular: a single class inheritance, but modules mixed in without limit. Where other languages stack interfaces or rig multiple inheritance, Ruby organizes and shares with the same tool — the module — and lets you choose, each time, between naming and grafting. This first issue lays the vocabulary; the whole volume will dig into the call it opens: identity or capability?

Prochain numéro : la chaîne d'ancêtres & prepend. On a dit que include « insère le module dans la chaîne » ; le № 2 montre cette chaîne en clair — l'ordre exact qu'ancestors révèle, et que super remonte pas à pas. include place le module sous la classe, prepend le place au-dessus (pour envelopper une méthode existante). Comprendre cet ordre, c'est savoir précisément quelle méthode répondra à un message — et boucler sur le « tout est message » du Vol 1.Next issue: the ancestor chain & prepend. We said include 'inserts the module into the chain'; № 2 shows that chain in the open — the exact order ancestors reveals, and that super walks step by step. include places the module below the class, prepend places it above (to wrap an existing method). Grasping that order is knowing precisely which method will answer a message — and looping back to Vol 1's 'everything is a message'.