In Ruby, what is a class macro such as `alert_on :cpu, above: 90` in a class body, and when does it run?
answer
- a class body is executable code
- self is the class there
- a class-level method, often from extend
- runs once, at load time
- records config or defines methods
basics
~20 sA class macro is an ordinary method called on the class: inside a class body self is the class, so alert_on :cpu, above: 90 calls a class-level method. It runs once, as the class body executes, recording settings or defining methods.
solid answer
~50 sIn Ruby a class body is code that runs top to bottom when the file loads, with `self` set to the class being defined. A bare call like `alert_on :cpu, above: 90` is therefore a method call on the class — a **class macro**. The method is a class-level method: written as `def self.alert_on` in a base class, or defined in a module the class `extend`s. It runs each time the class body executes — normally once, at load — and typically does two things: records configuration in the class (for example an array of rules) and generates instance methods with `define_method`, such as `cpu_breached?`. Built-ins like `attr_accessor`, `private` and `include` work the same way — they are methods of `Module`, not keywords. Because it is a normal call, the macro must already exist on the line where it is used.
code
ruby · 16 linesmodule AlertMacros
def alert_on(metric, above:)
(@alerts ||= []) << [metric, above]
define_method("#{metric}_breached?") { |value| value > above }
end
def alerts = @alerts || []
end
class WebChecks
extend AlertMacros # self here is WebChecks
alert_on :cpu, above: 90 # WebChecks.alert_on(:cpu, above: 90)
end
WebChecks.alerts # => [[:cpu, 90]]
WebChecks.new.cpu_breached?(95) # => truego deeper
Recall that a class body runs as code with self set to the class, so a bare call there is a method call on the class.
Explain where the macro method lives, def self.x or an extended module, and the two jobs it usually does.
Discuss timing traps and why macro state stored in class-level instance variables is not inherited by subclasses.
Judge when a macro vocabulary is worth adding to a codebase versus plain method calls or configuration objects.
## A class body is ordinary code In many languages a class declaration is a static description. In Ruby, `class WebChecks ... end` is **executable**: Ruby runs the lines inside it, in order, when the file is loaded. While it runs, `self` is the class object `WebChecks` itself. That is why a line with no receiver inside a class body is a method call **on the class**. Such a call, used to declare something about the class, is called a **class macro**. It looks like a keyword, but it is just a method. ## Where the macro method lives A macro must be a method the class object responds to — a class-level method. Two common ways to provide one: - **In a base class:** `def self.alert_on(metric, above:)` in `BaseChecks`; subclasses inherit it. - **In a module of macros:** define `alert_on` as an instance method of `AlertMacros`, then write `extend AlertMacros` in the class. `extend` adds the module's methods to the class object only, so instances do not get `alert_on`. The built-in macros follow the same rule: `attr_accessor`, `private`, `include`, `extend` and `define_method` are all methods defined on `Module`, called on `self` in the class body. ## What a macro usually does 1. **Records configuration** on the class, often in a class-level instance variable such as `@alerts`, so the tool can later ask `WebChecks.alerts`. 2. **Generates instance methods**, for example `define_method("#{metric}_breached?")`, so each declaration gives instances a matching method. 3. **Validates early**: raising `ArgumentError` for a bad option at load time surfaces the mistake on the line that made it. | Line in the class body | What Ruby actually does | |---|---| | `extend AlertMacros` | calls `WebChecks.extend(AlertMacros)` | | `alert_on :cpu, above: 90` | calls `WebChecks.alert_on(:cpu, above: 90)` | | `private` | calls `WebChecks.private`, changing default visibility for later `def`s | | `def cpu_limit` | defines an instance method; not a macro | ## Timing traps - **Order matters.** A macro called on line 2 whose `def self.alert_on` appears on line 5 raises `NoMethodError`: Ruby does not hoist method definitions. - **Methods defined later are not visible yet.** If the macro inspects instance methods of the class, methods written below the macro call do not exist when it runs. - **It runs once per class body execution.** Creating instances does not re-run it; reloading the file does. ## Inheritance of macro state A subclass inherits the instance methods a macro generated in its parent, but a **class-level instance variable** belongs to one class object only. If `ApiChecks < WebChecks` calls `alert_on :errors`, then `ApiChecks.alerts` starts from an empty list and sees only `:errors` unless the macro copies the parent's list. Libraries that want inherited configuration copy it explicitly when a subclass is created. ## Writing a good macro - **Use keyword arguments for options.** `def alert_on(metric, above:)` makes a missing option fail at load with `ArgumentError` (missing keyword: :above) on the user's line. - **Keep side effects on the class.** A macro should record data or define methods on `self`, not start threads or open connections while the file loads. - **Name generated methods predictably** (`cpu_breached?` from `:cpu`), so readers can find them without running the code. - **Guard against duplicates.** Declaring the same metric twice silently redefines the generated method; raising on a repeat is kinder. ## Why interviewers ask The question checks that a candidate sees through Ruby's declarative look: class bodies are code, `self` is the class, and "keywords" like `attr_accessor` are methods. A candidate who understands this can write their own macros and debug the ones libraries provide.
- Why does a subclass that calls the same macro not see its parent's recorded rules?The macro stores rules in a class-level instance variable, and each class object has its own. `ApiChecks.alerts` reads `ApiChecks`'s `@alerts`, which starts empty. Generated instance methods are inherited through normal lookup, but the recorded list is not unless the library copies it when the subclass is created.
- Is `attr_accessor` a keyword?No. It is a method defined on `Module` and called on `self` in the class body, exactly like a custom macro. It generates a reader and a writer instance method at the moment the line runs.
saying these in an interview costs you the question
- Class macros are special keywords handled by the parser.
- A class macro runs every time an instance is created.
- Ruby hoists def self.x so a macro can be used before it is defined.
- Inside a class body, self is a new instance of the class.