In Ruby, what happens when an object receives a call to a method that no class in its ancestor chain defines?
answer
- a second call, not an instant error
- name, args and block passed along
- private hook on BasicObject
- bare identifier gives NameError
- private with a receiver lands there too
basics
~20 sAfter lookup fails, Ruby calls method_missing on the receiver with the name as a Symbol, the arguments and the block. BasicObject's default raises NoMethodError, or NameError when the name was a bare identifier that could have been a local variable.
solid answer
~40 sRuby searches the receiver's class and ancestors; when nothing defines the name, it calls `method_missing(name, *args, &block)` on the same receiver. The default, a private method on `BasicObject`, raises: `NoMethodError` ("undefined method 'totl' for an instance of Order") for calls with a receiver, arguments or parentheses, and `NameError` ("undefined local variable or method") for a bare identifier, because that could have been a local. Calling a private method with an explicit receiver takes the same path and gives a "private method ... called" error. A class can override `method_missing` to answer such names, which are called ghost methods, and should call `super` for everything it does not handle.
code
ruby · 16 linesclass Order
def check_bare = totl
def check_paren = totl()
end
begin
Order.new.check_bare
rescue NameError => e
p e.class # => NameError
end
begin
Order.new.check_paren
rescue NameError => e
p e.class # => NoMethodError
endgo deeper
Recall that a failed lookup calls method_missing with the name, arguments and block, and that the default raises NoMethodError.
Explain why a bare identifier gives NameError, and that private calls with a receiver also route through method_missing.
Show that you read name, receiver and args on the exception when diagnosing ghost-method failures in production logs.
Treat method_missing as a hook with a contract: every override must hand unknown names back to super.
## The lookup, then the fallback When Ruby evaluates `receiver.name(args)`, it looks for a method called `name` in the receiver's class and then in each ancestor in turn. Usually it finds one and calls it. When **no** class or module in the chain defines `name`, Ruby does not raise straight away. It makes a second call on the same receiver: ```ruby receiver.method_missing(:name, *args, &block) ``` `method_missing` is an ordinary, **private** instance method defined on `BasicObject`, so every object has it. It receives: 1. the **name** that was not found, as a Symbol; 2. the **arguments** of the failed call, in order, including keywords; 3. the **block**, if the caller passed one. The default implementation raises an exception describing the failed call. Any class can override `method_missing` to answer names that have no method of their own; such names are often called **ghost methods**, because they work without existing in any method table. ## Which exception, and why it varies The default `method_missing` looks at **how** the missing name was written and picks the error class accordingly: | Call as written | Exception | Message (Ruby 4.0) | |---|---|---| | `order.totl` | `NoMethodError` | undefined method 'totl' for an instance of Order | | `totl()` or `self.totl` inside Order | `NoMethodError` | undefined method 'totl' for an instance of Order | | `totl` alone inside Order | `NameError` | undefined local variable or method 'totl' for an instance of Order | | `order.secret` where secret is private | `NoMethodError` | private method 'secret' called for an instance of Order | The third row is the interesting one. A bare identifier with no receiver, no arguments and no parentheses could be a **local variable** or a **method call**, so Ruby reports it as a `NameError` covering both. `NoMethodError` is a subclass of `NameError`, so `rescue NameError` catches all four rows. The last row shows that a call refused for **visibility** also goes through `method_missing`: the method exists, but calling a private method with an explicit receiver is not allowed, so Ruby treats it as missing for that call. Since Ruby 3.3 these messages say "an instance of Order" instead of calling `inspect` on the receiver, and since 3.4 they quote names with a straight single quote rather than a backtick. ## What the exception carries The raised exception is useful for debugging and for code that inspects it: - `NameError#name` — the missing name as a Symbol; - `NameError#receiver` — the object the call was made on; - `NoMethodError#args` — the arguments of the failed call; - `NoMethodError#private_call?` — whether the call had no explicit receiver. ## A tiny example of overriding it ```ruby class Order def method_missing(name, *args, &block) return "legacy #{name}" if name.start_with?("legacy_") super end end Order.new.legacy_total # => "legacy legacy_total" Order.new.totl # NoMethodError, raised by the default via super ``` The `super` call hands every name the class does not recognise back to the default, so typos still fail loudly with the normal message. ## Where this shows up in real code Most Rubyists meet `method_missing` long before they write one: - **Delegating wrappers** forward unknown names to a wrapped object. - **Dynamic finders and query helpers** in libraries parse the name, such as a prefix plus a field. - **Configuration and record objects** expose their keys as methods. In each case the mechanism is the one above: the lookup fails, `method_missing` runs, and the library either answers or passes the call on with `super`. ## Common misconceptions - **"Ruby raises before any user code runs."** It first calls `method_missing`, which a class can override. - **"method_missing is only for missing methods."** Private methods called with an explicit receiver also end up there. - **"A misspelt method always gives NoMethodError."** A bare, argument-less identifier gives `NameError`. - **"method_missing is public."** It is a private method of `BasicObject`, so `obj.method_missing(:x)` from outside is itself refused. ## What an interviewer is listening for The expected answer: lookup walks the ancestors, fails, and Ruby calls `method_missing` with the name, arguments and block; the default raises `NoMethodError`, or `NameError` for a bare identifier. Mentioning that overriding classes must fall back to `super` for names they do not handle is a strong finish.
- Why does rescue NameError also catch a NoMethodError?`NoMethodError` is a subclass of `NameError`, so a rescue clause for `NameError` matches both. Rescue `NoMethodError` specifically if a bare-identifier `NameError` should still propagate, for example to catch a typo in a local variable name.
- Which exception attributes help you see what the failed call looked like?`NameError#name` gives the missing name and `NameError#receiver` the object it was sent to. `NoMethodError#args` lists the arguments, and `NoMethodError#private_call?` says whether the call had no explicit receiver. They are handy in error reporting and in tests that assert on a missing-method failure.
saying these in an interview costs you the question
- Ruby raises NoMethodError before any user code can react
- a bare misspelt identifier always raises NoMethodError
- method_missing is a public method of Object
- calling a private method with a receiver never reaches method_missing
- rescue NoMethodError also catches every NameError