In Ruby, when a nesting module and a superclass both define DELIMITER, which one does a bare DELIMITER resolve to, and why does an inherited method ignore the subclass's override?
answer
- lexical scope wins over inheritance
- only each nesting entry's own table
- then ancestors of Module.nesting.first
- method body keeps its definition scope
- self.class::DELIMITER for dynamic lookup
basics
~20 sRuby checks each module in Module.nesting (own constants only) before the ancestors, so the enclosing module's DELIMITER wins over the superclass's. Constant lookup is fixed where code is written, so an inherited method keeps its own scope; use self.class::DELIMITER for a per-subclass value.
solid answer
~40 sA bare constant is resolved in a fixed order: first the **own constant table** of every entry in `Module.nesting`, innermost first; then the **ancestors** of the innermost class or module; then `Object`. So inside `module Ingest; module CSV; class Parser < BaseParser`, a bare `DELIMITER` finds `Ingest::CSV::DELIMITER` before `BaseParser::DELIMITER`, even though the superclass is "closer" by inheritance. The same lexical rule explains the classic trap: `BaseParser#delimiter` returning `DELIMITER` was compiled inside `BaseParser`, so it returns `BaseParser`'s value even when called on a subclass that defines its own `DELIMITER`. To let subclasses override a constant, look it up through the receiver: `self.class::DELIMITER`, which searches the subclass's ancestors at call time.
code
ruby · 15 linesmodule Ingest
class BaseParser
DELIMITER = ","
def delimiter = DELIMITER # fixed lexical scope
def delimiter_for_class = self.class::DELIMITER # looked up on the receiver
end
class TsvParser < BaseParser
DELIMITER = "\t"
end
end
parser = Ingest::TsvParser.new
parser.delimiter # => ","
parser.delimiter_for_class # => "\t"go deeper
Remember that a constant used without :: is looked up in the modules around the code first, and in the superclass only after that.
Explain the three steps: each Module.nesting entry's own table, then the ancestors of the innermost class, then Object, and show which DELIMITER wins in the example.
Diagnose the inherited-method trap from a bug report, explain that constants are resolved lexically while methods dispatch on the receiver, and fix it with self.class::NAME or a class method.
Set a codebase rule for overridable values: constants for fixed facts, class-level methods or explicit self.class:: lookups for anything subclasses customise, so the behaviour does not hinge on file layout.
## The search order for a bare constant When Ruby meets a bare constant such as `DELIMITER` (no `::` in front), it searches three places in order and returns the first hit: 1. **The lexical scope.** Every entry in `Module.nesting`, innermost first, but only each entry's **own** constant table. The ancestors of those enclosing modules are not consulted at this step, and the top level is not part of it. 2. **The ancestors** of the innermost class or module (`Module.nesting.first`): its prepended and included modules, then the superclass chain. 3. **`Object`**, which holds top-level constants. For a class it is reached through its ancestors; for a module Ruby checks it explicitly after the ancestors. If all three miss, Ruby calls `const_missing` on that innermost module, which by default raises `NameError`. ## Lexical scope beats inheritance Consider a data-import gem with a base parser and a CSV-specific namespace: ```ruby module Ingest class BaseParser DELIMITER = "," def delimiter = DELIMITER end module CSV DELIMITER = ";" class Parser < BaseParser def split(line) = line.split(DELIMITER) end end end ``` Inside `Parser#split`, `Module.nesting` is `[Ingest::CSV::Parser, Ingest::CSV, Ingest]`. `Parser` has no own `DELIMITER`, so step 1 moves on to `Ingest::CSV`, finds `";"` and stops. The superclass `BaseParser` is never consulted, although many developers expect inheritance to be searched first. | Reference in `Parser#split` | Result | Found in | |---|---|---| | `DELIMITER` | `";"` | `Ingest::CSV` (lexical) | | `self.class::DELIMITER` | `","` | `BaseParser` (ancestor of `Parser`) | | `BaseParser::DELIMITER` | `","` | `BaseParser` | ## Inherited methods keep their own scope The lexical scope belongs to the **code**, not to the object running it. `BaseParser#delimiter` was written inside `class BaseParser`, so its nesting is `[Ingest::BaseParser, Ingest]` forever. Now add a subclass: ```ruby module Ingest class TsvParser < BaseParser DELIMITER = "\t" end end Ingest::TsvParser.new.delimiter # => "," ``` The call runs `BaseParser`'s method body, whose lexical scope reaches `BaseParser`'s own `DELIMITER` first. `TsvParser::DELIMITER` is not in that scope and is not an ancestor of `BaseParser`, so it is never seen. This is the constant version of a "template method": it does not work with bare constants. ## Constants are not dispatched like methods The trap exists because constants and methods follow different rules, and interviews often ask for the contrast: | | Method call `delimiter` | Bare constant `DELIMITER` | |---|---|---| | Decided by | the receiver's class at call time | the lexical scope where the code is written | | Subclass override | takes effect | ignored by code in the superclass | | First place searched | the receiver's singleton class and ancestors | the enclosing modules' own tables | | Miss | `method_missing`, then `NoMethodError` | `const_missing`, then `NameError` | A developer who reasons about constants the way they reason about methods will expect `TsvParser` to win, and will be surprised. ## Making a constant overridable When subclasses are meant to supply their own value, look the constant up through the receiver: - `self.class::DELIMITER` resolves at call time in the receiver's class and its ancestors, so `TsvParser` gets `"\t"` and `BaseParser` gets `","`. - An explicit `::` path skips the lexical scope, does not fall back to top-level constants for a class, and refuses constants marked with `private_constant`. - A class-level method (`def self.delimiter = ","` overridden in each subclass) is often clearer than a constant when the value is really configuration. ## Diagnosing a surprising constant When a constant resolves to an unexpected value in a real codebase: - Print `Module.nesting` at the line in question to see the lexical scope actually in effect. - Check `Module.nesting.first.ancestors` for the second step of the search. - Look for compact definitions (`class Ingest::CSV::Parser`) that removed a namespace from the nesting, and for methods defined in a superclass that read constants the subclasses redefine. - Replace an ambiguous bare name with a qualified one (`Ingest::CSV::DELIMITER`) so the answer no longer depends on where the code sits.
- Inside the nested Ingest::CSV::Parser, if neither Parser nor Ingest::CSV defines DELIMITER but both module Ingest and superclass BaseParser do, which one wins?`Ingest::DELIMITER`. `Ingest` is part of `Module.nesting`, so its own table is checked in the lexical step, before the ancestors of `Parser`. `BaseParser` is only reached in the second step, after every lexical entry has missed.
- Does a block passed to Ingest::CSV::Parser.class_eval see Parser's constants by bare name?No. A block keeps the lexical scope where it was written, so bare constants inside it resolve against the caller's nesting, not `Parser`'s. A string passed to `class_eval` is compiled with `Parser` as its scope and does see them.
saying these in an interview costs you the question
- Ruby searches the superclass before the enclosing modules for a bare constant
- An inherited method sees the subclass's constant because self is the subclass
- Lexical lookup checks the ancestors of every module in Module.nesting
- self.class::DELIMITER behaves exactly like a bare DELIMITER
- Constant lookup is decided at call time from the receiver, like method dispatch