In Ruby, why must a LibraryLoan method write self.due_date = day instead of due_date = day to call its due_date= setter?
answer
- = at the end of the name
- no receiver means local variable
- self works for private setters
- assignment returns the right side
- no endless setters
basics
~20 sInside a method, name = value without a receiver always assigns a local variable, so the due_date= setter never runs. An explicit receiver, self.due_date = day, makes Ruby call the method, and self works even when the setter is private.
solid answer
~40 sA method whose name ends in `=`, such as `due_date=`, is a **setter**: `loan.due_date = day` calls it with `day`. The parser treats `x = value` as a method call only when there is an **explicit receiver**; a bare `due_date = day` inside another method is always **local-variable assignment**, so the setter is silently skipped and `@due_date` keeps its old value. Writing `self.due_date = day` fixes it, and it works even when the setter is `private`, because private methods may be called with a literal `self` receiver. Two more rules follow from setters being assignment syntax: the expression always evaluates to the **right-hand side**, whatever the setter returns, and a setter cannot be defined as an endless method (`def due_date=(day) = ...` is a `SyntaxError`).
code
ruby · 27 linesclass LibraryLoan
attr_reader :due_date
def initialize(due_date)
@due_date = due_date
end
def extend_broken
due_date = @due_date + 14 # local variable; setter skipped
end
def extend_fixed
self.due_date = @due_date + 14
end
private
def due_date=(day)
@due_date = day
end
end
loan = LibraryLoan.new(10)
loan.extend_broken
p loan.due_date # => 10
loan.extend_fixed
p loan.due_date # => 24go deeper
Remember that a setter is a method ending in = and that inside the class you call it as self.name = value.
Explain the parser rule that a receiver-less assignment is always a local, why self works for private setters, and that assignment returns the right-hand side.
Recognise the silent bug in review, since nothing raises, and keep setters free of side effects callers would need to check.
Weigh setters against explicit command methods in public APIs, favouring names that make side effects and failure modes visible.
## Setters are ordinary methods with = in the name Ruby lets a method name end in `=`. Such a method is called a **setter** (or assignment method), and Ruby lets you call it with assignment syntax: ```ruby class LibraryLoan def due_date=(day) @due_date = day end end loan = LibraryLoan.new loan.due_date = 20 # calls due_date=(20) ``` The space before `=` is allowed; `loan.due_date = 20` and `loan.due_date=(20)` call the same method. ## Why due_date = day never calls the setter The parser must decide what `due_date = day` means **without an explicit receiver**. Its rule is fixed: a bare `name = value` is always a **local variable assignment**. 1. Inside `extend_loan`, the line `due_date = @due_date + 14` creates a local variable named `due_date`. 2. The local disappears when the method returns. 3. The setter never runs, so `@due_date` keeps its old value, and no error or warning points at the bug. The fix is to give the call a receiver: `self.due_date = @due_date + 14`. ## Receivers and visibility | Call form | Public `due_date=` | Private `due_date=` | |---|---|---| | `loan.due_date = 20` from outside | calls the setter | `NoMethodError` (private method called) | | `self.due_date = 20` inside the class | calls the setter | calls the setter | | `due_date = 20` inside the class | assigns a local variable | assigns a local variable | Private methods may be called either without a receiver or with the **literal** `self` as receiver. That exception is what keeps private setters usable: without a receiver they would be parsed as locals. ## The value of a setter call An assignment expression **always evaluates to the right-hand side**. Even if the setter returns something else, the caller sees the assigned value: - `def due_date=(day); @due_date = day; :ok; end` still makes `loan.due_date = 7` evaluate to `7`. - Returning a status from a setter is therefore pointless; use a normal method, such as `renew`, when the caller needs a result. ## Other rules and conventions - **No endless setters.** `def due_date=(day) = @due_date = day` fails with `invalid method name; a setter method cannot be defined in an endless method definition`. - **Compound assignment calls both methods.** `loan.renewals += 1` calls `renewals` to read the value, then `renewals=` with the sum. - **Multiple assignment works with setters.** `loan.due_date, loan.renewals = 30, 0` calls `due_date=` and then `renewals=`. - **Name setters after the attribute.** `due_date=` sets a due date; an action with side effects, such as extending a loan, reads better as a verb (`renew`, `renew!`) than as a setter. ## Finding the silent bug Because nothing raises, the symptom is only that the value does not change. Three checks find it quickly: 1. **Run with `ruby -w`.** When the stray local is never read, the parser warns `assigned but unused variable - due_date` at that line. 2. **Search for receiver-less assignments** whose name matches a setter in the class; each one should read `self.name =`. 3. **Watch for shadowing.** After `due_date = ...`, a later bare `due_date` in the same method reads the local, not the reader method, so even the code that checks the value can be fooled. A test that calls the method and then reads the attribute through its public reader catches the bug where reading the code does not. ## How to answer in an interview Explain the parser rule first (no receiver means local variable), show the silent failure, give the `self.` fix, and add that it works for private setters. Mentioning that assignment returns the right-hand side shows you know setters are syntax, not ordinary calls.
- What does loan.renewals += 1 call?It expands to `loan.renewals = loan.renewals + 1`, so it calls the reader `renewals`, adds 1, and passes the sum to the setter `renewals=`. Both methods must exist, and inside the class the same rule applies: `self.renewals += 1` calls the methods, while a bare `renewals += 1` works on a local variable.
- Why can a private setter be called as self.due_date = day when other private methods reject an explicit receiver?Ruby allows a private method to be called with the literal `self` as its receiver. Without that allowance, a private setter would be unusable: the only receiver-less form, `due_date = day`, is parsed as a local variable assignment. Any receiver other than the literal `self`, such as a variable that happens to hold the same object, still raises `NoMethodError`.
saying these in an interview costs you the question
- due_date = day inside the class calls the setter because the method exists
- self.due_date = day raises NoMethodError when the setter is private
- loan.due_date = day evaluates to whatever the setter method returns
- A setter can be written as a one-line endless method like any other
- self. on the left of = is needed only when the setter is private