skip to content

Subclass & Definition Hooks

inherited, method_added and const_missing let a class react when it is subclassed, gains a method or meets an unknown constant. Interviewers ask about registries and hook pitfalls.

on this pageshow

explore

questions

4

In Ruby, how would you auto-register every subclass of a ReportFormat base class, and when is Class#subclasses enough instead of an inherited hook?

level: middleimportance: must knowfreq 40%

answer

  1. def self.inherited(subclass); super
  2. fires before the subclass body
  3. grandchildren trigger it too
  4. subclasses: direct and still alive
  5. Class#subclasses since 3.1

basics

~20 s

Override self.inherited(subclass) on ReportFormat, call super, and append the subclass to a registry; it fires at class Csv < ReportFormat, before Csv's body runs. Class#subclasses (Ruby 3.1+) returns only direct, still-alive subclasses, with no extra metadata.

solid answer

~50 s

The classic registry is a class-level list filled by the `inherited` hook: in the base class, `def self.inherited(subclass)` calls `super` and appends `subclass` to `REGISTRY`. Ruby calls it as soon as `class Csv < ReportFormat` creates the class — the constant is already assigned, but the body has **not run yet**, so `Csv`'s methods and constants such as `EXT` do not exist inside the hook; read them later, when the registry is used. Because `inherited` is itself inherited, `class Tsv < Csv` also reaches the base hook unless `Csv` overrides it without `super`. Ruby 3.1 added `Class#subclasses`, which returns the **direct** subclasses only, includes anonymous ones, and does not keep them alive, so garbage-collected classes may disappear from it. It is enough for a one-level hierarchy where order and extra data do not matter; a hand-kept registry wins when you need all descendants, a stable order, keys or explicit removal.

go deeper

for a junior

Recall that def self.inherited(subclass) on the base class runs whenever a subclass is created.

for a middle

Explain the timing before the subclass body, why super matters, and what Class#subclasses returns in Ruby 3.1 and later.

for a senior

Handle test pollution, reloading and load order in a registry, and choose between a registry and Class#subclasses on concrete needs.

for a principal

Weigh implicit registration against explicit registration calls for plugin systems that other teams extend.

## The goal: find every report format A reporting tool has a base class `ReportFormat` and one subclass per output format (`Csv`, `Json`, `Pdf`). The tool must list the formats without a hand-maintained array that someone forgets to update. Ruby offers two ways. ## Option 1: a registry filled by inherited `Class#inherited(subclass)` is a private hook Ruby calls on the parent whenever a subclass is created. ```ruby class ReportFormat REGISTRY = [] def self.inherited(subclass) super REGISTRY << subclass end end class Csv < ReportFormat EXT = "csv" end ReportFormat::REGISTRY # => [Csv] ``` Facts about timing: 1. Ruby creates the class object and assigns the constant `Csv`. 2. Ruby calls `ReportFormat.inherited(Csv)` — `subclass.name` is already `"Csv"`. 3. Only then does the body run, defining `EXT` and the methods. So inside the hook `Csv::EXT` raises `NameError` and `Csv.instance_methods(false)` is empty. Store the class and read its configuration later, when the registry is used. Other behaviours: - **Grandchildren**: `inherited` is a class method, so `Tsv < Csv` calls `Csv.inherited`, which is the base's version unless `Csv` overrides it. A registry therefore collects all descendants. - **`super` matters**: if `Csv` defines its own `self.inherited` without `super`, the base hook stops seeing `Csv`'s children. - **`Class.new(ReportFormat)`** fires the hook too, before the block given to `Class.new` runs. ## Option 2: Class#subclasses Ruby 3.1 added `Class#subclasses`, which asks the class which classes inherit from it directly. - **Direct only**: `ReportFormat.subclasses` includes `Csv` but not `Tsv < Csv`; walk it recursively for descendants. - **Anonymous classes included**: `Class.new(ReportFormat)` appears as `#<Class:0x...>`. - **No strong references**: the parent does not keep subclasses alive, so a class that nothing else references may vanish from the result after garbage collection. - **No documented order** and no room for extra data such as a format key. ## Comparing them | Concern | `inherited` registry | `Class#subclasses` | |---|---|---| | Available since | any Ruby | Ruby 3.1 | | Scope | all descendants (while hooks call `super`) | direct subclasses only | | Order | definition order, under your control | not documented | | Keeps classes alive | yes, the array holds them | no | | Extra data (key, priority) | easy: store a hash | none | | Removing an entry | you delete it | only by dropping references | | Code to maintain | a hook and a list | none | ## Storing more than the class A registry does not have to be an array. A hash keyed by a format name makes lookup direct: - register the class in `inherited` under a provisional key such as `subclass.name`; - or let each subclass declare its key with an explicit call in its body (`register_as :csv`), which runs after the hook and can overwrite the provisional entry; - raise on duplicate keys, so two formats cannot claim the same name silently. ## Pitfalls in production - **Test pollution**: tests that create throwaway subclasses add them to a strong registry forever; either clean the registry in teardown or filter anonymous classes. - **Code reloading**: a registry keeps the old class objects after a reload unless the reloader clears it (framework reloaders are their own topic). - **Load order**: a subclass is registered only once its file has been loaded; a registry is only as complete as what was required. ## Choosing Use `Class#subclasses` when you need a quick, one-level listing and the classes are named constants that stay loaded. Keep an `inherited` registry when you need all levels, a stable order, a key per format, or control over removal.

  • Why can't the inherited hook read the subclass's EXT constant?
    Ruby calls `inherited` right after creating the class and assigning its constant, but before running the class body. `EXT` is defined by the body, so inside the hook `subclass::EXT` raises `NameError`. Register the class and read `EXT` later, when the registry is queried.
  • How do you list all descendants with Class#subclasses?
    Recurse: collect `klass.subclasses`, then each of their `subclasses`, and so on. Remember that classes no longer referenced may already have been garbage-collected and will be missing from the result.

saying these in an interview costs you the question

  • inherited runs after the subclass body, so its methods are available.
  • Class#subclasses returns grandchildren as well as direct subclasses.
  • Class#subclasses keeps every subclass alive for the life of the process.
  • A subclass's inherited override without super still lets the base hook run.
  • Class.new(Base) bypasses the inherited hook.
open as a page

In Ruby, which hook methods run when a class is subclassed, gains or loses an instance method, or gains a singleton method?

level: juniorimportance: should knowfreq 30%

basics

~10 s

Ruby calls inherited(subclass) on the parent when a class is subclassed, method_added(name) after an instance method is defined, method_removed or method_undefined after remove_method or undef_method, and singleton_method_added(name) when an object gains a singleton method.

open as a page

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?

level: middleimportance: should knowfreq 25%

basics

~20 s

Ruby 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.

open as a page

In Ruby, how can a `logged` class-level call use method_added to wrap the next method defined, and why can a naive version raise SystemStackError?

level: seniorimportance: should knowfreq 20%

basics

~20 s

logged sets a flag, and method_added(name) wraps the method just defined. If the wrapper is defined with define_method in the same class before the flag is cleared, that definition fires method_added again, endlessly. Clear the flag first, or prepend the wrapper.

open as a page