In Ruby, why does attr_reader :heating? raise, and how do you give a Thermostat a heating? predicate instead?
answer
- attr names must be variable-like
- NameError: invalid attribute name
- hand-write the ? method
- or alias_method onto a reader
- TrivialAccessors allows predicates
basics
~10 sattr_* names must be valid local variable or constant names, so heating? raises NameError (invalid attribute name). Write the predicate by hand, def heating? = @heating, or alias it to a generated heating reader.
solid answer
~40 sThe `attr_*` helpers derive the instance variable from the name, and `@heating?` is not a legal instance variable, so they accept only names that are valid local variable or constant names. `attr_reader :heating?` therefore raises `NameError` with `invalid attribute name 'heating?'` while the class body runs. The idiomatic fix is a hand-written predicate: `def heating? = @heating`, or `@heating == true` if callers need a strict boolean. When a plain reader is also wanted, `attr_reader :heating` plus `alias_method :heating?, :heating` gives both names. RuboCop's `Style/TrivialAccessors` normally pushes trivial hand-written readers towards `attr_reader`, but its `AllowPredicates` option defaults to `true`, so `def heating? = @heating` is accepted as is.
code
ruby · 14 linesclass Thermostat
attr_reader :target
def initialize(target:, current:)
@target = target
@current = current
@heating = current < target
end
def heating? = @heating
def cooling? = @current > @target
end
Thermostat.new(target: 21, current: 18).heating? # => truego deeper
Recall that attr_reader cannot take a name ending in a question mark and that predicates are written by hand with def.
Explain why: the helpers need a valid instance variable name, check it at class-load time, and raise NameError with invalid attribute name.
Show judgment on predicate design: truthy versus strict Boolean returns, derived predicates computed from state, and whether to expose both heating and heating?.
Consider which predicate conventions a team should standardise and enforce in review or lint configuration, so that Boolean state reads consistently across a codebase.
## Why the helpers refuse question marks `attr_reader`, `attr_writer` and `attr_accessor` build each method from one name: the reader is called `name`, the writer `name=`, and both use the instance variable `@name`. For that to work, `@name` must be a legal instance variable, so Ruby checks the name first: - It must be usable as a **local variable name** (such as `heating`) or a **constant name** (such as `Mode`). - `heating?` and `reset!` are method-only names. There is no `@heating?` variable. - A bad name raises **`NameError`** with the message `invalid attribute name 'heating?'`, at the moment the class body runs, so the class fails to load. ```ruby class Thermostat attr_reader :heating? # NameError: invalid attribute name 'heating?' end ``` ## Writing the predicate by hand A **predicate reader** is a method whose name ends in `?` and answers a yes-or-no question about the object. For a Boolean stored in `@heating` there are three common spellings: | Definition | Returns | |---|---| | `def heating? = @heating` | whatever is stored: `true`, `false` or `nil` | | `def heating? = @heating == true` | exactly `true` or `false` | | `attr_reader :heating` plus `alias_method :heating?, :heating` | the stored value, under two names | Points to weigh: 1. **Truthy is usually enough.** Callers test the result with `if`, where `nil` and `false` both count as false. 2. **Strict booleans help at boundaries.** When the value is serialized or compared with `==`, returning exactly `true` or `false` avoids a `nil` leaking out. 3. **Two names or one.** Keeping both `heating` and `heating?` doubles the public surface. Most classes expose only the predicate. ## Derived predicates Predicates do not have to mirror a stored flag. They often compute an answer from other state: ```ruby def heating? = @current < @target def idle? = !heating? && !cooling? ``` That is another reason they are hand-written: the answer belongs to the object's logic, not to a single variable. ## What the linter says RuboCop's `Style/TrivialAccessors` cop flags hand-written methods that only return or assign an instance variable of the same name, suggesting `attr_reader` or `attr_writer` instead. Its `AllowPredicates` option defaults to `true`, so a trivial `def heating? = @heating` is **not** flagged: the cop acknowledges that the `attr_*` helpers cannot produce that name. ## Answering it in an interview A complete answer covers three steps: 1. **The rule.** The `attr_*` helpers need a name that can become an instance variable; `heating?` cannot, so they raise `NameError` when the class loads. 2. **The fix.** Write the predicate with `def`, returning the stored flag or a strict Boolean, or alias it to a generated reader with `alias_method`. 3. **The judgment.** Decide whether callers need `heating` at all, whether `nil` may leak from the predicate, and whether the answer is stored or computed. ## Common mistakes - Writing `attr_accessor :heating?` hoping for both `heating?` and `heating?=`. It raises the same `NameError`. - Using the old two-argument `attr :heating, true`, which is a deprecated spelling of `attr_accessor :heating` and produces no predicate. - Returning a non-Boolean such as a Time from a predicate, which works in `if` but surprises callers who print or compare the result.
- In Ruby, does attr_accessor :Mode with a capitalized name work?Yes. The helpers accept constant-style names as well as local-style ones, so it defines `Mode` and `Mode=` backed by `@Mode`. It is legal but unconventional, and calling the reader needs an explicit receiver or parentheses, because a bare `Mode` is read as a constant.
- In Ruby, when should a predicate like heating? return exactly true or false rather than the stored value?When the result crosses a boundary where `nil` and `false` differ: JSON output, equality checks against `true`, or APIs documenting a Boolean. Inside ordinary `if` conditions, returning the stored truthy or falsy value is enough.
saying these in an interview costs you the question
- attr_reader :heating? defines a heating? method reading @heating
- attr_reader :heating? fails with a SyntaxError
- attr_accessor :heating? defines heating? and heating?=
- RuboCop always flags a hand-written heating? that returns @heating
- attr :heating, true defines a heating? predicate