In Ruby, how should a mixin require its including class to define a method, and why does checking method_defined? inside self.included usually fail?
answer
- hook fires at the include line
- class body not finished yet
- stub that raises, host overrides it
- class precedes included module
- prepend flips who wins
basics
~20 sThe included hook runs at the include line, before the class body defines its methods, so an eager method_defined? check rejects valid hosts. Declare the contract lazily: a stub raising a clear error, overridden by the host's own method.
solid answer
~50 sA mixin often depends on a method the host must supply — say `export_rows` for an `Exportable` module. Checking `base.method_defined?(:export_rows)` in `self.included` fails because the hook fires at the `include` line, usually at the top of the class body, before the `def` below it has run. The idiomatic contract is lazy: the module defines a stub `export_rows` that raises an error naming the host class, and the host's own definition wins because a class comes before its included modules in `ancestors`. That only holds for `include`: a prepended module sits in front of the class, so its stub would shadow the host's method. Many codebases raise `NotImplementedError` in the stub, but it is a `ScriptError`, not a `StandardError`, so a bare `rescue` does not catch it; a custom `StandardError` subclass avoids that surprise. Back the contract with a test that includes the module into each host.
code
ruby · 11 linesmodule Exportable
def self.included(base)
return if base.method_defined?(:export_rows)
raise ArgumentError, "#{base} must define #export_rows"
end
end
class Ledger
include Exportable # raises ArgumentError: the def below has not run yet
def export_rows = [[1, 2]]
endgo deeper
Remember that a class body runs top to bottom, so methods defined below an include line do not exist yet when the included hook runs.
Explain why a raising stub works: the host class precedes the included module in ancestors, so the host's own method is found first.
Cover the prepend inversion, the ScriptError ancestry of NotImplementedError, and backing a lazy contract with tests so missing methods fail in CI.
Decide how a team documents and enforces mixin contracts — stubs, error classes, contract tests — so implicit interfaces stay discoverable as hosts multiply.
## The contract a mixin carries Many mixins provide behaviour built on top of a method they do not define themselves. An `Exportable` module might offer `export`, which calls `export_rows`, and expect every host class to implement `export_rows`. Ruby has no interface declaration for this, so the mixin has to state the requirement in code. The question is **when** to check. ## Why an eager check in the hook fails A Ruby class body is ordinary code, executed top to bottom. `include Exportable` is a method call on that line; it runs `append_features` and then `Exportable.included(base)` immediately. Any `def` further down the body has **not run yet**. So: - `base.method_defined?(:export_rows)` returns `false` for a perfectly valid host that defines the method below the `include`. - Moving `include` to the bottom of every class "fixes" it but is fragile and surprising. - Reopened classes and methods added later by other code are invisible at hook time as well. `Module` offers no callback for "the class body has finished"; only a `TracePoint` on the `:end` event can observe that moment, which is heavy machinery for a contract check. The hook is simply the wrong moment for it. ## The lazy contract: a stub the host overrides The robust pattern defines the required method in the module as a **stub that raises**: 1. The module defines `export_rows`, whose body raises an error naming `self.class` and the missing method. 2. The host defines its own `export_rows`. 3. Method lookup searches the class before its included modules, so the host's version is found first and the stub is never reached. 4. A host that forgets the method gets a clear error the first time the behaviour is used, instead of a `NoMethodError` from deep inside the mixin. | Mixing call | Where the module sits | Who wins for `export_rows` | |---|---|---| | `include Exportable` | after the class in `ancestors` | the host's method | | `prepend Exportable` | before the class in `ancestors` | the module's stub, which raises | The table shows the trap: the stub pattern depends on `include`. A module designed to be prepended must not define stubs for methods the host provides; it should call `super` or check `defined?(super)` instead. ## Choosing the error class - **`NotImplementedError`** is widely used for this, but it descends from `ScriptError`, not `StandardError`. A bare `rescue` or `rescue => e` does not catch it, which can crash a job runner that expects to rescue application errors. Ruby's own documentation describes it as the error for features not implemented on the current platform. - **A custom subclass of `StandardError`** (for example `Exportable::MissingHookError`) is caught by ordinary rescue clauses and documents the contract by its name. - **`NoMethodError`** raised manually mimics a genuinely missing method; it is a `StandardError` too, but it hides that the mixin, not a typo, caused the failure. ## Making the contract visible early Lazy failure is correct at runtime but late in development. Complement it with: - A test that exercises the mixin against every host, so a missing method fails in CI. - A class-level declaration the host calls **after** its definitions, if an explicit check point is wanted. - Documentation at the top of the module listing the required methods. ## Why not rely on NoMethodError alone? Without a stub, a host that forgets the method still fails — `export` calls `export_rows` and Ruby raises `NoMethodError`. That works, but the message names only the missing method and the receiver, not the mixin that required it, and a reader of the module cannot see its contract without reading every call site. A stub turns an implicit dependency into a named, searchable one, and its message can say exactly which module needs what. The cost is small: one method per requirement, overridden by every correct host. ## Summary - The `included` hook fires at the `include` line, before the rest of the body. - Enforce host methods lazily with a raising stub, relying on class-before-module lookup. - Do not use stubs in modules meant for `prepend`. - Prefer a `StandardError` subclass for the stub's error so ordinary rescue clauses see it.
- In Ruby, why does a raise NotImplementedError stub escape a bare rescue in the caller?`NotImplementedError` inherits from `ScriptError`, which sits directly under `Exception`, not under `StandardError`. A bare `rescue` and `rescue => e` catch only `StandardError` descendants, so the error propagates. Rescue it by name, or raise a custom `StandardError` subclass in the stub instead.
- In Ruby, how can a prepended module depend on a host method without a stub?A prepended module sits before the class, so its methods run first and reach the host's version with `super`. It should not define the host's required method itself; its wrapper can call `super` and, if the host might lack the method, check `defined?(super)` first and raise a clear error when it is missing.
saying these in an interview costs you the question
- Validates required host methods with method_defined? inside self.included.
- Believes the module's stub overrides the host's method when the module is included.
- Uses the raising-stub pattern in a module meant to be prepended.
- Assumes a bare rescue catches NotImplementedError like any application error.
- Expects a Module callback like included to fire when the class body ends.