In Ruby, when is Module#const_missing called, what happens to its return value, and how does Module#const_source_location help find a constant?
answer
- called on the module where lookup failed
- default raises NameError: uninitialized constant
- return value used, not stored
- const_set to stop repeat calls
- [file, line], [] for C, nil if absent
basics
~20 sRuby calls const_missing(name) on the module where a constant lookup failed; the default raises NameError. An override's return value becomes the expression's value but is not stored, so it runs again next time. const_source_location returns the defining file and line.
solid answer
~50 sWhen a constant reference such as `Formats::Xml`, or a bare `Xml` inside a class, finds nothing through the lexical scopes and ancestors, Ruby calls `const_missing(:Xml)` on the module where the lookup started — `Formats` for the scoped form, the innermost enclosing class or module for the bare form. The default implementation raises `NameError: uninitialized constant Formats::Xml`. An override can return any object, and that object is used as the value of the expression, but Ruby does not store it: the next reference misses again and calls `const_missing` again, unless the hook defines the constant with `const_set` or by loading a file. Old autoloaders used this; `autoload` replaced them. For the opposite question — where *is* this constant defined — `Module#const_source_location(:Xml)` returns `[path, line]`, `[]` for constants defined in C, and `nil` if the name is not found.
go deeper
Recall that an unknown constant raises NameError, and that const_missing is the hook Ruby calls just before that.
Explain which module receives the hook, why its return value is not stored, and what const_source_location returns.
Explain why const_missing-based loading is fragile and uncached, and use const_source_location to resolve duplicate constants.
Decide whether any const_missing override belongs in shared code, given typo hiding and interference between libraries.
## When const_missing fires Ruby resolves a constant by searching the **lexical scope** (the `module`/`class` blocks the code is written in) and then the **ancestors** of the relevant class. If everything misses, Ruby does not raise straight away; it first calls the hook **`Module#const_missing(name)`** with the name as a Symbol: - For a **scoped** reference `Formats::Xml`, it is called on `Formats`. - For a **bare** reference `Xml` in code inside `module Deep; class Inner`, it is called on the innermost enclosing class or module, `Deep::Inner`. The default implementation raises **`NameError`** with the familiar message `uninitialized constant Formats::Xml`. ## What an override's return value does An override may return any object, and Ruby uses it as the value of the constant expression: ```ruby module Formats def self.const_missing(name) name # return the Symbol end end Formats::Xml # => :Xml ``` Three consequences: 1. **Nothing is stored.** The constant is still undefined; the next `Formats::Xml` misses again and calls the hook again. 2. **No caching at the call site.** CRuby deliberately skips filling its inline constant cache after a `const_missing` call, so a hot path that relies on the hook pays the full lookup every time. 3. **Define it to stop the repeats.** A hook that calls `const_set(name, value)`, or that `require`s a file defining the constant, turns later references into ordinary hits. ## Why it is rarely the right tool now Older autoloaders overrode `Object.const_missing` to load a file named after the constant on first use. The approach has known weaknesses: - The hook receives only the **name and one module**, not the full lexical nesting, so it must guess which of several candidate files was meant. - A hook that returns something for every name **hides typos** that would otherwise raise `NameError`. - Several libraries overriding the same hook must all call `super`, or they break each other. Today `Module#autoload` handles lazy loading natively (its own topic), and `const_missing` is kept for narrow cases such as a helpful error message or a deliberate proxy namespace. ## Finding where a constant is defined The reverse question — which file and line defined this constant — is answered by **`Module#const_source_location`**: | Call | Result | |---|---| | `Object.const_source_location("ReportFormat")` | `["lib/report_format.rb", 1]` | | `Formats.const_source_location(:Csv)` | `[path, line]`, searching ancestors by default | | `Formats.const_source_location(:Csv, false)` | `nil` if `Csv` is only in an ancestor | | `Object.const_source_location("String")` | `[]`: defined in C, no Ruby source | | `Object.const_source_location("Nope")` | `nil`: no such constant | Two rules from its documentation are worth remembering: - A **reopened** class keeps the location of its first definition; reopening is not a new definition. - A **reassigned** constant (which warns) reports the location of the last assignment. Nested names such as `"Formats::Csv"` are accepted, which makes it handy in a console when two gems might define the same constant. ## Debugging an uninitialized constant When a `NameError: uninitialized constant` appears, a short routine usually finds the cause: 1. Read which module the message names: `Formats::Xml` means the lookup started at `Formats`, a bare name means the lexical scope of the failing line. 2. Check whether the file that defines the constant was loaded at all, or loaded later than the reference. 3. If the constant exists under another namespace, `const_source_location` on each candidate shows which file defined which one. 4. Check for a `const_missing` override that swallows or re-raises the miss, since it changes both the behaviour and the message. ## What the interviewer is after A middle-level candidate should know that `const_missing` is a hook with a `NameError` default, that returning a value does not define the constant, and that `const_source_location` is the tool for tracing a constant's origin.
- How would you make a const_missing override stop being called for the same name?Define the constant inside the hook: compute the value, call `const_set(name, value)` on the module, and return it. Later references then find the constant through normal lookup and never reach the hook.
- What does const_source_location return for a class that two files reopen?The file and line of the first `class` statement that created it; reopening is not treated as a new definition. Only reassigning the constant itself moves the reported location.
saying these in an interview costs you the question
- Returning a value from const_missing defines the constant permanently.
- The default const_missing returns nil for unknown constants.
- const_missing receives the full lexical nesting of the reference.
- const_source_location reports the last file that reopened the class.
- const_source_location raises NameError for C-defined constants.