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

method_missing, ou répondre à ce qu'on n'a pas écrit method_missing, or answering what you never wrote

Au bout de la chaîne d'ancêtres, quand rien n'a répondu, Ruby appelle method_missing. Le redéfinir intercepte les messages inconnus — mais jamais sans son jumeau respond_to_missing?, sinon l'objet ment au duck typing. Et seulement pour un ensemble ouvert : le connu se génère. At the end of the ancestor chain, when nothing answered, Ruby calls method_missing. Redefining it intercepts unknown messages — but never without its twin respond_to_missing?, or the object lies to duck typing. And only for an open set: the known is generated.

AudienceAudience
Dev tenté par l'objet magique, prêt à en mesurer le prix Dev tempted by the magic object, ready to weigh its price
Format
Self-paced
ChapitresChapters
5
Date
Avr 2026 Apr 2026
≈ 18 min ●●●○ method_missingrespond_to_missing?Proxy
SommaireContents ·
№ 01CadrageFraming3 min

method_missing est ce qui s'exécute au bout de la chaîne.method_missing is what runs at the end of the chain.

Le Vol 6 ouvre la métaprogrammation — du code qui écrit du code — et on y entre par prudence, pas par éblouissement. Premier outil, et le plus exposé à l'abus : method_missing. Le Vol 5 a montré qu'un message parcourt la chaîne d'ancêtres jusqu'au premier qui répond. Mais que se passe-t-il quand personne ne répond ? Ruby ne lève pas tout de suite : il envoie un dernier message, method_missing, tout au bout de la chaîne. Par défaut, BasicObject#method_missing lève le NoMethodError qu'on connaît. Le redéfinir, c'est intercepter les messages inconnus avant l'erreur — répondre à des méthodes qu'on n'a jamais écrites.Vol 6 opens metaprogramming — code that writes code — and we enter through caution, not dazzle. First tool, and the most abuse-prone: method_missing. Vol 5 showed a message walks the ancestor chain to the first that answers. But what happens when no one answers? Ruby doesn't raise right away: it sends one last message, method_missing, at the very end of the chain. By default, BasicObject#method_missing raises the NoMethodError we know. Redefining it intercepts unknown messages before the error — answering methods you never wrote.

Le dernier recours, avant le NoMethodErrorThe last resort, before the NoMethodError
"abc".wat
# NoMethodError: undefined method 'wat' for an instance of String
#   ↑ Ruby a parcouru TOUTE la chaîne d'ancêtres (Vol 5), rien n'a répondu —
#     alors il appelle method_missing au bout, qui par défaut lève l'erreur

class Ghost
  def method_missing(name, *args, &block)   # on intercepte AVANT l'erreur
    "tu as appelé #{name} avec #{args.inspect}"
  end
end

Ghost.new.anything(1, 2)   # => "tu as appelé anything avec [1, 2]"
Ghost.new.wat              # => "tu as appelé wat avec []"
method_missing termine la chaînemethod_missing ends the chain
Ghost
›
Object
›
BasicObject
›
method_missing

Le message a traversé toute la chaîne sans trouver de méthode. method_missing est le filet au bout : redéfini, il décide quoi faire ; laissé tel quel, il lève. Ce n'est pas une exception détournée — c'est un message comme un autre, le tout dernier.The message crossed the whole chain without finding a method. method_missing is the net at the end: redefined, it decides what to do; left as is, it raises. It's not a hijacked exception — it's a message like any other, the very last one.

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

« Si je définis method_missing, je définis toujours son jumeau respond_to_missing?. » Le couple est obligatoire : sans le second, l'objet répond à des messages mais prétend ne pas les connaître. On verra au chapitre suivant pourquoi ce mensonge casse le duck typing du Vol 3."If I define method_missing, I always define its twin respond_to_missing?." The pair is mandatory: without the second, the object answers messages but claims not to know them. The next chapter shows why that lie breaks Vol 3's duck typing.

Entrer par la prudence.Entering through caution.

La métaprog est le volume le plus exposé à l'abus, et method_missing son premier piège : c'est le réflexe paresseux qui fait « marcher » un objet magique sans qu'on écrive les méthodes. La puissance est réelle — proxies, délégateurs, finders dynamiques — mais chaque message intercepté est un message invisible au lecteur et au grep. On apprend l'outil et son prix dans le même numéro.Metaprogramming is the most abuse-prone volume, and method_missing its first trap: it's the lazy reflex that makes a magic object "work" without writing the methods. The power is real — proxies, delegators, dynamic finders — but each intercepted message is a message invisible to the reader and to grep. We learn the tool and its price in the same issue.

№ 02La surpriseThe surprise4 min

Sans son jumeau, l'objet répond mais jure qu'il ne sait pas.Without its twin, the object answers but swears it can't.

method_missing seul crée un objet schizophrène : il exécute c.host sans broncher, mais c.respond_to?(:host) répond false. Pourquoi ? Parce que respond_to? consulte les méthodes réellement définies, et host n'en est pas une — elle est interceptée. L'objet ment au duck typing du Vol 3 : tout le code qui demande « sais-tu faire X ? » avant d'appeler X le rejette à tort. respond_to_missing? est le correctif obligatoire : il déclare quels messages interceptés l'objet honore vraiment, et répare du même coup respond_to?, method, la délégation — toute la machinerie qui s'appuie dessus.method_missing alone makes a schizophrenic object: it runs c.host without blinking, but c.respond_to?(:host) returns false. Why? Because respond_to? consults the actually-defined methods, and host isn't one — it's intercepted. The object lies to Vol 3's duck typing: any code asking 'can you do X?' before calling X wrongly rejects it. respond_to_missing? is the mandatory fix: it declares which intercepted messages the object truly honors, and repairs respond_to?, method, delegation — the whole machinery that relies on it.

Il répond, mais respond_to? dit falseIt answers, but respond_to? says false
class Config
  def initialize(data) = @data = data
  def method_missing(name, *args)
    @data.fetch(name) { super }      # répond aux clés connues, sinon NoMethodError
  end
end

c = Config.new(host: "localhost", port: 5432)
c.host                  # => "localhost"   ça MARCHE
c.port                  # => 5432

c.respond_to?(:host)    # => false   ← MENSONGE : l'objet répond pourtant
[c].select { |x| x.respond_to?(:host) }   # => []   le duck typing (Vol 3) cassé
c.method(:host)         # NameError: undefined method 'host'   ← method() ment aussi

L'objet « marche » en apparence, et c'est le piège : tout le code défensif qui interroge respond_to? avant d'appeler — sérialiseurs, formulaires, case sur capacités — le traite comme s'il ne savait rien faire. method(:host) échoue aussi, et un SimpleDelegator qui l'enveloppe refuse de transmettre host. Seul &:host passe — il envoie le message sans rien demander. L'objet vit à moitié hors du protocole.The object "works" on the surface, and that's the trap: all the defensive code querying respond_to? before calling — serializers, forms, capability cases — treats it as if it could do nothing. method(:host) fails too, and a SimpleDelegator wrapping it refuses to forward host. Only &:host gets through — it sends the message without asking anything. The object lives half outside the protocol.

Un objet qui répond sans l'avouer ment au duck typing — et le code qui demande avant d'agir le croit muet.An object that answers without admitting it lies to duck typing — and code that asks before acting believes it mute.
№ 03La mécaniqueThe mechanics5 min

Toujours rendre la main par super — et le cas qui le justifie.Always hand back via super — and the case that justifies it.

method_missing reçoit le nom du message, ses arguments et son bloc. La règle d'or : ne traiter que ce qu'on revendique, et appeler super pour tout le reste — sinon on avale silencieusement chaque faute de frappe, et le NoMethodError disparaît. Le couple complet appelle donc super des deux côtés : dans method_missing pour lever proprement, dans respond_to_missing? pour déléguer la question. Le cas où tout cela est légitime, plutôt que paresseux : le proxy. Un objet dont on ne peut PAS connaître les méthodes à l'avance, parce qu'il enveloppe une cible quelconque — là, method_missing est le bon outil, pas un raccourci.method_missing receives the message name, its arguments and its block. The golden rule: handle only what you claim, and call super for everything else — otherwise you silently swallow every typo, and the NoMethodError vanishes. The full pair therefore calls super on both sides: in method_missing to raise cleanly, in respond_to_missing? to delegate the question. The case where all this is legitimate, rather than lazy: the proxy. An object whose methods you CAN'T know in advance, because it wraps an arbitrary target — there, method_missing is the right tool, not a shortcut.

La signature, et le super non négociableThe signature, and the non-negotiable super
def method_missing(name, *args, &block)
  #   name  : le Symbol du message reçu (:host)
  #   args  : les arguments positionnels
  #   block : le bloc éventuel
  if intercepte?(name)
    repond(name, *args, &block)
  else
    super              # ESSENTIEL : rend la main → vrai NoMethodError
  end
end
Le proxy : le cas vraiment légitimeThe proxy: the genuinely legitimate case
# Le cas légitime : un proxy qui délègue à une cible quelconque
class Proxy
  def initialize(target) = @target = target

  def method_missing(name, *args, &block)
    if @target.respond_to?(name)
      @target.public_send(name, *args, &block)   # on fait suivre
    else
      super                                       # sinon, erreur honnête
    end
  end

  def respond_to_missing?(name, include_private = false)
    @target.respond_to?(name, include_private) || super
  end
end

Proxy.new([1, 2, 3]).map { |x| x * 2 }   # => [2, 4, 6]   délégué à l'Array
Quand on ne PEUT pas lister les méthodes.When you CAN'T list the methods.

Un proxy enveloppe une cible inconnue à l'écriture : impossible d'énumérer ses méthodes d'avance, donc method_missing qui demande @target.respond_to? est justifié. Note l'usage de public_send (Vol 1) plutôt que send, pour ne pas forcer les méthodes privées. Le super final garde les vrais inconnus en erreur. C'est l'emploi noble du mécanisme — l'ensemble des messages est ouvert, on ne peut pas le générer à l'avance.A proxy wraps a target unknown at authoring time: you can't enumerate its methods ahead, so method_missing asking @target.respond_to? is justified. Note public_send (Vol 1) rather than send, so you don't force private methods. The trailing super keeps genuine unknowns erroring. This is the noble use of the mechanism — the message set is open, you can't generate it ahead.

Retiens la frontière : method_missing est pour l'ensemble ouvert et imprévisible (un proxy, un client d'API dont les champs viennent du serveur). Dès que l'ensemble des messages est connu et fini — cinq champs, dix accesseurs — il n'a plus rien à y faire : c'est le terrain du prochain chapitre, et de define_method.Keep the boundary: method_missing is for the open, unpredictable set (a proxy, an API client whose fields come from the server). The moment the message set is known and finite — five fields, ten accessors — it has no business there: that's the next chapter's ground, and define_method's.

Le kata — écrivez le proxy, il sera jugéThe kata — write the proxy, it will be judged

Six tests, un objet à écrire. Les trois premiers tombent avec le method_missing qu'on écrit tous du premier coup. Les trois suivants sont ceux du numéro : ils ne demandent rien d'exotique — juste que respond_to? dise la vérité, que method(:…) rende quelque chose d'appelable, et qu'un code poli qui demande avant d'envoyer passe par votre proxy au lieu de le contourner sans bruit. Vous saurez que le jumeau est obligatoire quand vous aurez vu vos trois lignes rouges, pas avant.Six tests, one object to write. The first three fall to the method_missing we all write on the first try. The last three are the issue's: they ask for nothing exotic — only that respond_to? tell the truth, that method(:…) return something callable, and that polite code which asks before sending goes through your proxy instead of silently around it. You'll know the twin is mandatory once you've seen your three red lines, not before.

… ou écrivez-le ici, en Ruby… or write it here, in Rubyun vrai Ruby 3.4, dans votre navigateur · rien n'est envoyéa real Ruby 3.4, in your browser · nothing is sent
⌘/Ctrl + ↵
№ 04Le piègeThe trap4 min

method_missing pour un ensemble connu : de la magie inutile.method_missing for a known set: pointless magic.

Le contre-exemple obligatoire du numéro. On a cinq champs connus à l'avance, et on dégaine method_missing « pour ne pas écrire cinq accesseurs ». Mais ici l'ensemble est fini et nommable : intercepter au lieu de définir ne fait que cacher. Les méthodes n'apparaissent dans aucune introspection, un grep host ne les trouve pas, chaque appel fait un détour par method_missing, les stack traces deviennent cryptiques, et on doit maintenir le couple à la main. define_method dans une boucle génère exactement ces cinq méthodes — réelles, listables, grep-ables — et respond_to? marche tout seul. La règle : method_missing pour l'ouvert, define_method pour le connu.The issue's mandatory counter-example. You have five fields known in advance, and you reach for method_missing 'to avoid writing five accessors'. But here the set is finite and nameable: intercepting instead of defining only hides. The methods show in no introspection, a grep host won't find them, every call detours through method_missing, stack traces turn cryptic, and you maintain the pair by hand. define_method in a loop generates exactly those five methods — real, listable, grep-able — and respond_to? works on its own. The rule: method_missing for the open, define_method for the known.

Intercepter ce qu'on connaît : invisible, coûteuxIntercepting the known: invisible, costly
# Le piège : method_missing pour 5 champs CONNUS d'avance
class Settings
  FIELDS = %i[host port user pass ssl]
  def initialize(h) = @h = h

  def method_missing(name, *args)
    FIELDS.include?(name) ? @h[name] : super
  end
  def respond_to_missing?(name, priv = false)
    FIELDS.include?(name) || super
  end
end

Settings.instance_methods(false)   # => [:method_missing]
# la seule méthode exposée est l'intercepteur : aucun :host, :port…
# $ grep "def host" .              → rien : introuvable
# coût : un détour par method_missing à CHAQUE appel, des stack traces
# cryptiques, et le couple à gérer à la main — pour 5 noms qu'on connaît
define_method : réel, traçable, grep-abledefine_method: real, traceable, grep-able
# define_method : on génère de VRAIES méthodes, en boucle
class Settings
  FIELDS = %i[host port user pass ssl]
  def initialize(h) = @h = h

  FIELDS.each do |field|
    define_method(field) { @h[field] }   # une vraie méthode par champ
  end
end

Settings.instance_methods(false).sort        # => [:host, :pass, :port, :ssl, :user]
Settings.new(host: "x").respond_to?(:host)   # => true   sans rien de plus
# (.sort : instance_methods ne garantit aucun ordre de retour)
# traçable, listable, respond_to? marche seul, zéro détour à l'appel
Le contre-exemple obligatoire.The mandatory counter-example.

Le critère est simple : l'ensemble des messages est-il ouvert (imprévisible, venu du dehors) ou fermé (connu à l'écriture) ? Fermé ⇒ define_method : de vraies méthodes que l'introspection liste, que grep trouve, qui n'ajoutent aucun détour à l'appel. method_missing sur du connu, c'est le réflexe paresseux qui troque cinq lignes lisibles contre un objet magique que personne — ni lecteur, ni outil, ni toi dans six mois — ne saura déboguer. La magie n'a de valeur que là où l'explicite est impossible.The test is simple: is the message set open (unpredictable, coming from outside) or closed (known at authoring)? Closed ⇒ define_method: real methods introspection lists, grep finds, that add no detour at call time. method_missing on the known is the lazy reflex trading five readable lines for a magic object no one — not a reader, not a tool, not you in six months — can debug. Magic is worth it only where the explicit is impossible.

Intercepter ce qu'on pourrait définir, c'est cacher cinq méthodes derrière une trappe — invisible au lecteur, au grep, au débogueur.Intercepting what you could define hides five methods behind a trapdoor — invisible to the reader, the grep, the debugger.
№ 05Bilan & éditoWrap-up & editorial2 min

Le couple est obligatoire, et l'ensemble doit être ouvert.The pair is mandatory, and the set must be open.

method_missing s'exécute au bout de la chaîne d'ancêtres, quand aucune méthode n'a répondu. Le redéfinir intercepte les messages inconnus — mais jamais sans respond_to_missing?, sinon l'objet ment au duck typing. On appelle super des deux côtés pour garder les vrais inconnus en erreur. Et on réserve l'outil à l'ensemble ouvert (proxy, champs venus du dehors) : dès que les messages sont connus et finis, define_method génère de vraies méthodes, et c'est le sujet du prochain numéro.method_missing runs at the end of the ancestor chain, when no method answered. Redefining it intercepts unknown messages — but never without respond_to_missing?, or the object lies to duck typing. You call super on both sides to keep genuine unknowns erroring. And you reserve the tool for the open set (proxy, fields from outside): the moment messages are known and finite, define_method generates real methods, the next issue's subject.

La questionThe questionLa règleThe ruleLe réflexeThe reflex
Je définis method_missingI define method_missingAlors je définis aussi respond_to_missing?.Then I also define respond_to_missing?.Un mécanisme, deux moitiés — jamais l'une seule.One mechanism, two halves — never just one.
Un message non revendiquéAn unclaimed messageJ'appelle super des deux côtés.I call super on both sides.Sinon j'avale chaque faute de frappe en silence.Or I swallow every typo silently.
L'ensemble est ouvert ?Is the set open?Proxy, délégateur, champs externes : oui, légitime.Proxy, delegator, external fields: yes, legitimate.public_send pour faire suivre, pas send.public_send to forward, not send.
L'ensemble est connu ?Is the set known?Alors define_method, pas method_missing.Then define_method, not method_missing.De vraies méthodes : listables, grep-ables.Real methods: listable, grep-able.
method_missing n'est pas un raccourci pour écrire moins — c'est un filet pour ce qu'on ne peut pas nommer d'avance.method_missing isn't a shortcut to write less — it's a net for what you can't name ahead.
Mot de l'éditeurFrom the editor

Ouverture du Vol 6 : la puissance se mesure à son prix.Opening Vol 6: power is measured by its price.

La métaprogrammation est la suite directe du Vol 1 : si tout est objet, alors les classes et les méthodes aussi se manipulent à l'exécution. Mais c'est le volume le plus exposé à l'abus, et on a choisi d'y entrer par son piège le plus tentant. method_missing donne l'illusion d'un objet qui « sait tout faire » sans qu'on écrive rien — et c'est exactement ce qui le rend dangereux : il déplace du code hors de portée du lecteur, du grep, du débogueur. La leçon du numéro n'est pas « n'utilisez jamais method_missing » ; c'est : il a un seul emploi noble — l'ensemble ouvert qu'on ne peut pas générer — et partout ailleurs, une méthode explicite battait la magie.Metaprogramming is the direct sequel to Vol 1: if everything is an object, then classes and methods too are manipulated at runtime. But it's the most abuse-prone volume, and we chose to enter through its most tempting trap. method_missing gives the illusion of an object that 'can do anything' without writing a thing — and that's exactly what makes it dangerous: it moves code out of reach of the reader, the grep, the debugger. The issue's lesson isn't 'never use method_missing'; it's: it has one noble use — the open set you can't generate — and everywhere else, an explicit method beat the magic.

Prochain numéro : define_method & la génération. On a dit que pour un ensemble connu, on génère de vraies méthodes ; le № 2 montre comment. define_method, class_eval, ce que fait réellement attr_accessor, et les DSL déclaratifs. C'est la métaprog qui produit du code visible et traçable plutôt que de l'intercepter — la moitié lumineuse du volume, celle qui crée des méthodes que respond_to?, grep et le débogueur retrouvent.Next issue: define_method & generation. We said that for a known set, you generate real methods; № 2 shows how. define_method, class_eval, what attr_accessor really does, and declarative DSLs. It's the metaprogramming that produces visible, traceable code rather than intercepting it — the bright half of the volume, the one that creates methods respond_to?, grep and the debugger find.