In Ruby, why is a prepended module calling super safer than an alias_method chain for patching a method, and what happens when both patch one method?
answer
- rename the original vs stand in front
- super reaches the original body
- prepending twice is a no-op
- alias chain loaded twice recurses
- prepend first, alias second: SystemStackError
basics
~20 sA prepended module sits in front of the class, so its super reaches the original without renaming anything, and stacked patches compose. An alias chain snapshots the original; loaded twice, or added after a prepend on one method, it recurses into SystemStackError.
solid answer
~40 sAn **alias chain** copies the current method under a new name (`alias_method :minutes_without_log, :minutes`), redefines `minutes`, and calls the copy. It adds names to the class and depends on what `minutes` meant at the moment of aliasing: load the same patch twice and the second alias captures the already patched method, so the chain calls itself. A **prepend patch** puts a module in front of the class, and its `minutes` calls `super` to reach the class's own method; prepending the same module again is a no-op and several patches stack in order. Mixing the styles on one method is the classic gem conflict: if one gem prepends and a second then alias-chains, the alias captures the prepended method, the prepended method's `super` reaches the new chain, and every call ends in `SystemStackError`.
code
ruby · 17 linesclass Integer
def minutes = self * 60
end
# gem_a: prepend patch
module GemA
def minutes = super
end
Integer.prepend(GemA)
# gem_b, loaded later: alias chain
class Integer
alias_method :minutes_without_b, :minutes # captures GemA#minutes
def minutes = minutes_without_b
end
2.minutes # SystemStackError: stack level too deepgo deeper
Recall the two shapes: alias_method plus a new def, versus a prepended module whose method calls super.
Explain that the alias copies a snapshot while super looks up the next method, and why prepending twice changes nothing.
Walk the prepend-then-alias recursion step by step and explain why load order makes it appear only after an upgrade.
Set a rule that new patches use namespaced prepended modules, and audit dependencies for alias chains on the same methods.
## Two ways to wrap an existing method A **monkey patch** often wants to run extra code around an existing method — log it, fix an edge case, add a unit — and still call the original. Ruby offers two techniques. **The alias chain** (the older style): 1. `alias_method :minutes_without_log, :minutes` stores the current body of `minutes` under a second name. 2. A new `def minutes` replaces the original entry. 3. The new body calls `minutes_without_log` to reach the old behaviour. **The prepend patch** (the modern style): 1. Define a module with its own `minutes` that calls `super`. 2. `Integer.prepend(ThatModule)` inserts the module in front of `Integer` in the method-lookup chain. 3. Calling `5.minutes` finds the module's method first; its `super` continues the lookup and reaches `Integer`'s own `minutes`. ## Why prepend composes better | Concern | Alias chain | Prepend patch | |---|---|---| | Extra public names | adds `minutes_without_log` to the class | none; the module owns the wrapper | | Loading the patch twice | second alias captures the patched method, so it calls itself forever | prepending a module already in the chain does nothing | | Two independent patches | compose only if their alias names differ and neither file loads twice | stack: the last prepended runs first, each `super` reaches the next | | Arguments and block | the wrapper must forward them by hand | bare `super` forwards the same arguments and block | | Seeing who patched | method now lives in `Integer` | the module name appears in `Integer.ancestors` and in backtraces | The key difference is that an alias chain **copies a snapshot** of a method at one moment, while `super` **looks up** the next method each time it runs. A snapshot is only correct if nothing else touched the method before or after. ## The collision: prepend first, alias chain second This is the bug that appears when one gem uses `prepend` and a second, older gem uses an alias chain on the same method: 1. Gem A prepends a module whose `minutes` calls `super`. 2. Gem B runs `alias_method :minutes_without_b, :minutes` inside `Integer`. Lookup for `minutes` starts at `Integer`, whose chain now begins with Gem A's module, so the alias captures **Gem A's wrapper**, not the original. 3. Gem B's `def minutes` is stored in `Integer`'s own table — behind the prepended module — replacing the original. 4. A call runs Gem A's wrapper, whose `super` reaches Gem B's method, which calls `minutes_without_b`, which is Gem A's wrapper again. The loop never ends, and Ruby raises `SystemStackError: stack level too deep`. The same two patches in the opposite order (alias chain first, prepend second) work, which is why the failure depends on load order and often appears only after an upgrade. ## Practical rules - **Write new patches with `prepend` and `super`**, and give the module a descriptive, namespaced name. - **Never mix styles on one method you do not own.** If a dependency already alias-chains a method, a prepend added before it loads can trigger the loop; check the dependencies' patches before adding yours. - **Make patch files idempotent.** A prepend is naturally idempotent; an alias chain needs a guard, which is another reason to avoid it. - **Test the patched method** after loading every gem, so a `SystemStackError` shows up in CI, not in production. ## What this question tests Interviewers use it to see whether a candidate knows *why* the Ruby community moved from alias chains to `prepend`: not style, but composition. A strong answer explains the snapshot-versus-lookup difference and can walk through the recursion step by step.
- Why does the same pair of patches work when the alias chain loads first?Then the alias captures `Integer`'s original body, the chained `minutes` replaces the entry in `Integer`, and the later prepended module's `super` reaches that chained method, which calls the original. Every step moves forward, so the call terminates; only the reverse order loops.
- What happens if a file with an alias-chain patch is loaded twice?The second `alias_method :minutes_without_log, :minutes` captures the already patched `minutes`, and the second `def minutes` calls it. The patched method now calls itself through the alias and raises `SystemStackError`. A prepend patch loaded twice is harmless, because prepending a module already in front of the class changes nothing.
saying these in an interview costs you the question
- An alias chain and a prepend on the same method always compose safely.
- Prepending the same module twice makes its wrapper run twice.
- Loading an alias-chain patch file twice is harmless.
- super in a prepended module skips the class and calls the superclass method.
- A prepend patch overwrites the class's own method-table entry.