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

La chaîne d'ancêtres, ou où Ruby cherche la méthode The ancestor chain, or where Ruby looks for the method

Résoudre un message, c'est parcourir ancestors jusqu'au premier ancêtre qui répond. include insère le module sous la classe, prepend au-dessus (pour envelopper), super appelle le maillon suivant. Le diamant n'existe pas : la chaîne est une liste linéarisée et lisible. Resolving a message is walking ancestors to the first ancestor that answers. include inserts the module below the class, prepend above (to wrap), super calls the next link. The diamond doesn't exist: the chain is a linearized, readable list.

AudienceAudience
Dev qui veut savoir exactement quelle méthode répond à un message Dev who wants to know exactly which method answers a message
Format
Self-paced
ChapitresChapters
5
Date
Avr 2026 Apr 2026
≈ 19 min ●●●○ Chaîne d'ancêtresprependsuper
SommaireContents ·
№ 01CadrageFraming3 min

Un message ne « trouve » pas sa méthode : il parcourt une liste.A message doesn't "find" its method: it walks a list.

Le № 1 a montré qu'include « insère le module dans la chaîne ». Voici cette chaîne en clair. ancestors révèle l'ordre exact dans lequel Ruby cherche une méthode quand un objet reçoit un message : sa classe d'abord, puis les modules qu'elle mixe, puis sa superclasse, et ainsi de suite jusqu'à BasicObject. Le premier ancêtre qui définit la méthode gagne ; les autres ne sont jamais consultés. C'est la mécanique concrète derrière le « tout est message » du Vol 1 — résoudre un message, c'est descendre cette liste jusqu'au premier qui répond.№ 1 showed that include 'inserts the module into the chain'. Here's that chain in the open. ancestors reveals the exact order in which Ruby looks up a method when an object receives a message: its class first, then the modules it mixes, then its superclass, and so on to BasicObject. The first ancestor that defines the method wins; the others are never consulted. It's the concrete mechanics behind Vol 1's 'everything is a message' — resolving a message is walking this list down to the first that answers.

ancestors : le chemin de recherche, dans l'ordreancestors: the lookup path, in order
String.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]
#     └ la classe  └ un mixin  └ la superclasse … jusqu'à la racine

# envoyer "abc".upcase, c'est PARCOURIR cette liste de gauche à droite
# et s'arrêter au PREMIER ancêtre qui définit upcase :
"abc".method(:upcase).owner    # => String     trouvé dès le 1er
"abc".method(:tap).owner       # => Kernel      trouvé plus loin
"abc".method(:equal?).owner    # => BasicObject à la toute fin
Le message descend la chaîneThe message walks the chain
String
›
Comparable
›
Object
›
Kernel
›
BasicObject

Pour "abc".upcase, Ruby s'arrête au premier maillon — String — qui définit upcase. Pour tap, il descend jusqu'à Kernel. La méthode n'est pas « dans l'objet » : elle est quelque part sur le chemin.For "abc".upcase, Ruby stops at the first link — String — that defines upcase. For tap, it goes down to Kernel. The method isn't "in the object": it's somewhere on the path.

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

« Quand je ne comprends pas quelle méthode s'exécute, je lis ancestors. » L'ordre n'est pas une devinette : c'est une liste qu'on peut afficher, et le premier ancêtre qui définit le message l'emporte. include, prepend et l'héritage ne font qu'insérer des maillons à des positions précises."When I don't understand which method runs, I read ancestors." The order isn't a guess: it's a list you can print, and the first ancestor defining the message wins. include, prepend and inheritance only insert links at precise positions.

MRO : Method Resolution Order.MRO: Method Resolution Order.

Ce que ancestors renvoie est l'ordre de résolution des méthodes de la classe. Tout le volume tient dans une idée : include, prepend et < (héritage) ne créent pas de magie — ils décident où, dans cette liste, un maillon s'insère. Savoir lire la chaîne, c'est savoir d'avance quelle méthode répondra, et ce que super appellera ensuite.What ancestors returns is the class's method resolution order. The whole volume fits one idea: include, prepend and < (inheritance) create no magic — they decide where, in this list, a link is inserted. Reading the chain is knowing in advance which method answers, and what super will call next.

№ 02La surpriseThe surprise4 min

include insère sous la classe ; prepend, au-dessus.include inserts below the class; prepend, above.

Les deux verbes mixent un module, mais à des positions opposées dans la chaîne — et la position décide de tout. include place le module juste sous la classe : ses méthodes ne s'exécutent que si la classe ne définit pas déjà le message. prepend le place juste au-dessus : ses méthodes passent en premier, et c'est ce qui permet d'envelopper une méthode existante (logging, mémoïsation, instrumentation) en redonnant la main à l'original par super. La même paire module/classe, deux comportements, selon un seul maillon de différence.Both verbs mix a module, but at opposite positions in the chain — and position decides everything. include puts the module just below the class: its methods run only if the class doesn't already define the message. prepend puts it just above: its methods go first, which is what lets you wrap an existing method (logging, memoization, instrumentation) and hand back to the original via super. The same module/class pair, two behaviors, one link of difference apart.

Sous la classe : la méthode de classe gagneBelow the class: the class method wins
module Loud
  def speak = super.upcase   # enrobe : appelle l'original, puis le crie
end

class Cat
  def speak = "meow"
  include Loud               # Loud va SOUS Cat dans la chaîne
end

Cat.ancestors   # => [Cat, Loud, Object, ...]
Cat.new.speak   # => "meow"   ← Cat#speak gagne, Loud#speak n'est PAS atteint
                #    include ne peut pas envelopper une méthode de la classe

Comme Loud est sous Cat, Ruby rencontre Cat#speak d'abord et s'arrête : Loud#speak est dans la chaîne mais jamais atteint. C'est la limite d'include — il ajoute des méthodes que la classe n'a pas, il ne peut pas envelopper celles qu'elle a.Since Loud is below Cat, Ruby meets Cat#speak first and stops: Loud#speak is in the chain but never reached. That's include's limit — it adds methods the class lacks, it can't wrap the ones it has.

Un maillon plus haut ou plus bas, et le même module change de pouvoir : ajouter, ou envelopper.One link higher or lower, and the same module changes power: to add, or to wrap.
№ 03La mécaniqueThe mechanics6 min

super remonte la chaîne d'un cran — et le diamant est déjà résolu.super climbs the chain one notch — and the diamond is already resolved.

super n'appelle pas « la méthode du parent » : il appelle la prochaine définition du même message dans la chaîne d'ancêtres, exactement le maillon suivant après celui qui s'exécute. Empiler des include et des super, c'est composer une pile de comportements dans l'ordre que ancestors affiche — et le dernier module inclus est le plus proche de la classe, donc consulté en premier. Quant au fameux « problème du diamant » de l'héritage multiple, Ruby le désamorce : un module mixé à plusieurs endroits n'apparaît qu'une fois dans la chaîne, à sa position la plus spécifique. Pas d'ambiguïté, pas d'appel en double.super doesn't call 'the parent's method': it calls the next definition of the same message in the ancestor chain, exactly the link after the one running. Stacking include and super composes a pile of behaviors in the order ancestors prints — and the last module included is closest to the class, so consulted first. As for multiple inheritance's famous 'diamond problem', Ruby defuses it: a module mixed in at several places appears only once in the chain, at its most specific position. No ambiguity, no double call.

super descend la chaîne, maillon par maillonsuper walks down the chain, link by link
module A; def hi = "A→" + super; end
module B; def hi = "B→" + super; end

class Base
  def hi = "Base"
end

class C < Base
  include A
  include B            # le DERNIER inclus est le plus proche de la classe
  def hi = "C→" + super
end

C.ancestors   # => [C, B, A, Base, Object, ...]
C.new.hi      # => "C→B→A→Base"
              #    super remonte la chaîne, un maillon à la fois
Le diamant résolu par linéarisationThe diamond resolved by linearization
module Walk; def move = "marche"; end

class Animal; include Walk; end
class Robot;  include Walk; end       # Walk inclus à DEUX endroits

# pas de duplication : Ruby LINÉARISE la chaîne, Walk n'apparaît qu'une fois
class Cyborg < Animal
  include Walk                         # déjà présent via Animal
end
Cyborg.ancestors
# => [Cyborg, Animal, Walk, Object, ...]   ← un seul Walk, à sa 1re position
#    le « diamant » est résolu : aucune ambiguïté, aucun appel en double
Une seule chaîne, jamais un arbre.One chain, never a tree.

Là où l'héritage multiple d'autres langages crée un arbre ambigu (par quel chemin atteint-on la méthode ?), Ruby aplatit tout en une seule liste ordonnée. Un module n'y figure qu'une fois, à sa première rencontre. Du coup super est parfaitement déterministe : il n'y a qu'un « maillon suivant » possible. Lis ancestors et tu connais, sans l'exécuter, l'ordre exact dans lequel les super vont s'enchaîner.Where other languages' multiple inheritance builds an ambiguous tree (by which path do we reach the method?), Ruby flattens everything into a single ordered list. A module appears once, at its first encounter. So super is perfectly deterministic: there's only one possible "next link". Read ancestors and you know, without running it, the exact order the supers will chain in.

Ce déterminisme est le vrai cadeau du modèle : une « capacité » mixée s'insère à un endroit connu de la chaîne, et super suit une route lisible. C'est exactement ce que le prochain chapitre va opposer à l'héritage profond — où la même capacité, modélisée comme une identité, éparpille le comportement sur plusieurs niveaux et brouille la route que super emprunte.This determinism is the model's real gift: a mixed-in 'capability' inserts at one known point of the chain, and super follows a readable route. That's exactly what the next chapter contrasts with deep inheritance — where the same capability, modeled as an identity, scatters the behavior across several levels and blurs the route super takes.

№ 04Le piègeThe trap4 min

Une tour d'héritage pour une capacité qu'un mixin disait en une ligne.A tower of inheritance for a capability a mixin stated in one line.

Le contre-exemple du numéro, et le piège du dev qui vient de l'héritage classique. Pour donner à Money la capacité d'être comparé, il bâtit une hiérarchie — Money est un Comparable_ est une Entity — et réimplémente >, <, == à la main, niveau par niveau. La chaîne s'allonge, super doit la traverser, et le protocole reste incomplet (il manque toujours between?, clamp…). Or « pouvoir être comparé » n'est pas une identité, c'est une capacité : include Comparable l'ajoute à un seul endroit connu de la chaîne, dérive toutes les opérations d'un unique <=>, et ne prétend pas que Money « est » autre chose que Money.The issue's counter-example, and the trap of the dev coming from classical inheritance. To give Money the capability of being compared, they build a hierarchy — Money is a Comparable_ is an Entity — and reimplement >, <, == by hand, level by level. The chain lengthens, super has to traverse it, and the protocol stays incomplete (between?, clamp… still missing). But 'can be compared' isn't an identity, it's a capability: include Comparable adds it at one known point of the chain, derives every operation from a single <=>, and never pretends Money 'is' anything other than Money.

L'héritage profond : capacité déguisée en identitéDeep inheritance: capability disguised as identity
# Modéliser une CAPACITÉ (« peut être comparé ») comme une IDENTITÉ
class Entity; end
class Comparable_ < Entity
  def >(o)  = (self <=> o) > 0      # on réimplémente l'ordre à la main…
  def <(o)  = (self <=> o) < 0
  def ==(o) = (self <=> o).zero?    # … et on en oublie : >=, <=, between?, clamp
end
class Money < Comparable_
  attr_reader :cents
  def initialize(c) = @cents = c
  def <=>(o) = cents <=> o.cents
end

Money.ancestors  # => [Money, Comparable_, Entity, Object, ...]
# Money EST une Entity EST un Comparable_ — trois niveaux pour une capacité,
# une tour d'héritage où super doit traverser, et un protocole incomplet
Un seul <=>, et Comparable fait le resteOne <=>, and Comparable does the rest
# La même capacité, mixée à UN endroit connu
class Money
  include Comparable               # une ligne : la capacité, pas une identité
  attr_reader :cents
  def initialize(c) = @cents = c
  def <=>(o) = cents <=> o.cents   # le SEUL contrat à fournir
end

Money.ancestors  # => [Money, Comparable, Object, ...]   un seul maillon ajouté
Money.new(5) < Money.new(9)        # => true
Money.new(5).clamp(Money.new(1), Money.new(3))  # >, <, ==, between?, clamp… gratis
Le contre-exemple obligatoire.The mandatory counter-example.

Avant d'hériter pour partager un comportement, demande : est-ce une identité (« Money est une Entity ») ou une capacité (« Money sait se comparer ») ? Une capacité se mixe : include Comparable insère un maillon dans la chaîne, dérive <, >, ==, between?, clamp d'un seul <=>, et garde ancestors lisible. La tour d'héritage, elle, éparpille le protocole sur plusieurs niveaux, allonge la route de super, et oublie la moitié des opérations.Before inheriting to share a behavior, ask: is it an identity ("Money is an Entity") or a capability ("Money knows how to compare itself")? A capability is mixed in: include Comparable inserts one link in the chain, derives <, >, ==, between?, clamp from a single <=>, and keeps ancestors readable. The inheritance tower scatters the protocol across levels, lengthens super's route, and forgets half the operations.

Hériter pour obtenir une capacité, c'est allonger la chaîne pour ce qu'un seul maillon mixé disait mieux.Inheriting to get a capability is lengthening the chain for what one mixed-in link said better.
№ 05Bilan & éditoWrap-up & editorial2 min

Quelle méthode répond ? Lis la chaîne — elle ne ment jamais.Which method answers? Read the chain — it never lies.

Résoudre un message, c'est parcourir ancestors de gauche à droite jusqu'au premier ancêtre qui définit la méthode. include insère le module sous la classe (il ajoute), prepend au-dessus (il enveloppe, super redonne la main). super appelle le maillon suivant, pas « le parent ». Et le diamant n'existe pas : la chaîne est une liste linéarisée, chaque module y figure une fois. Dès qu'un comportement te surprend, n'imagine pas — affiche ancestors.Resolving a message is walking ancestors left to right to the first ancestor defining the method. include inserts the module below the class (it adds), prepend above (it wraps, super hands back). super calls the next link, not 'the parent'. And the diamond doesn't exist: the chain is a linearized list, each module appears once. The moment a behavior surprises you, don't imagine — print ancestors.

L'outilThe toolCe qu'il fait à la chaîneWhat it does to the chainLe réflexeThe reflex
ancestorsancestorsAffiche l'ordre de résolution, en clair.Prints the resolution order, in the open.Un doute ? lis-la avant de deviner.In doubt? read it before guessing.
includeincludeInsère le module SOUS la classe : il ajoute.Inserts the module BELOW the class: it adds.Pour donner ce que la classe n'a pas.To give what the class lacks.
prependprependInsère AU-DESSUS : il enveloppe, super redescend.Inserts ABOVE: it wraps, super reaches down.Pour intercepter une méthode existante.To intercept an existing method.
supersuperAppelle le maillon suivant, pas « le parent ».Calls the next link, not 'the parent'.Suis la chaîne, pas l'arbre imaginé.Follow the chain, not the imagined tree.
include, prepend, l'héritage : trois façons d'insérer un maillon dans une liste qu'on peut lire. Aucune magie — un ordre.include, prepend, inheritance: three ways to insert a link into a list you can read. No magic — an order.
Mot de l'éditeurFrom the editor

La chaîne, c'est le « tout est message » du Vol 1 rendu visible.The chain is Vol 1's "everything is a message" made visible.

Ce numéro est le cœur du volume parce qu'il referme une boucle ouverte au tout début. Le Vol 1 disait : un objet reçoit un message et décide quoi en faire. On voit maintenant comment il décide — il parcourt sa chaîne d'ancêtres et prend la première définition rencontrée. include, prepend, l'héritage, super : tout ce vocabulaire qui semblait éparpillé n'est qu'une façon d'insérer des maillons à des positions précises dans cette liste, et de la remonter. La leçon n'est pas d'apprendre des règles ; c'est d'arrêter de deviner. La chaîne est imprimable, déterministe, sans ambiguïté de diamant — quand une méthode te surprend, la réponse est dans ancestors.This issue is the volume's heart because it closes a loop opened at the very start. Vol 1 said: an object receives a message and decides what to do. We now see how it decides — it walks its ancestor chain and takes the first definition met. include, prepend, inheritance, super: all that vocabulary that seemed scattered is just ways of inserting links at precise positions in this list, and climbing it. The lesson isn't to learn rules; it's to stop guessing. The chain is printable, deterministic, with no diamond ambiguity — when a method surprises you, the answer is in ancestors.

Prochain numéro : extend, méthodes de classe & composition. On a inséré des maillons dans la chaîne d'une classe ; le № 3 mixe au niveau d'un objet — méthodes de classe, décorateurs — puis pose l'arbitrage qui clôt le volume : avant d'hériter ou même de mixer, ai-je besoin d'ÊTRE cette chose, ou seulement de SAVOIR FAIRE ce qu'elle fait ? La composition, souvent, dit mieux la seconde.Next issue: extend, class methods & composition. We inserted links into a class's chain; № 3 mixes at the object level — class methods, decorators — then poses the call that closes the volume: before inheriting or even mixing, do I need to BE this thing, or only to KNOW HOW to do what it does? Composition often says the latter better.