skip to content

In Ruby, what is a class macro such as `alert_on :cpu, above: 90` in a class body, and when does it run?

level: juniorimportance: should knowfreq 40%

answer

  1. a class body is executable code
  2. self is the class there
  3. a class-level method, often from extend
  4. runs once, at load time
  5. records config or defines methods

basics

~20 s

A 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 s

In 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 lines
ruby
module 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)  # => true

go deeper

for a junior

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.

for a middle

Explain where the macro method lives, def self.x or an extended module, and the two jobs it usually does.

for a senior

Discuss timing traps and why macro state stored in class-level instance variables is not inherited by subclasses.

for a principal

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.