In Ruby, which methods do attr_reader, attr_writer and attr_accessor define, and what does each call return?
answer
- name and name= methods
- reader returns @name, nil if unset
- no instance variable created
- returns an Array of Symbols
- return value changed in Ruby 3.0
basics
~10 sattr_reader :temp defines temp, returning @temp; attr_writer :temp defines temp=, assigning @temp; attr_accessor defines both. Since Ruby 3.0 each call returns an Array of the defined method names as Symbols.
solid answer
~40 sThe three are methods on `Module` that generate instance methods from names given as Symbols or Strings. `attr_reader :target` defines `target`, which returns `@target`, or `nil` if it was never assigned. `attr_writer :target` defines `target=`, which assigns `@target`. `attr_accessor :target` defines both. Nothing is assigned at definition time: the instance variable appears only when the writer or `initialize` sets it. Since Ruby 3.0 each call returns an Array of the method names it defined, so `attr_accessor :target, :mode` returns `[:target, :target=, :mode, :mode=]`; before 3.0 it returned `nil`. The generated methods take the visibility of the section they are written in, and they do no validation: a writer stores whatever it is given.
code
ruby · 14 linesclass Thermostat
p attr_reader(:room) # => [:room]
p attr_accessor(:target, :mode) # => [:target, :target=, :mode, :mode=]
def initialize(room)
@room = room
end
end
t = Thermostat.new("lab")
t.target # => nil, never assigned
t.target = 21
t.target # => 21
t.room = "attic" # NoMethodError: no room= writer was definedgo deeper
Recall the three helpers and the methods each defines: name, name=, or both, reading and writing the matching instance variable.
Explain that nothing is assigned at definition, that readers return nil until then, and that since Ruby 3.0 the calls return the defined names as Symbols.
Show when generated methods are the wrong tool: validation in writers, copies or computed values in readers, and names ending in a question mark.
Weigh how freely a codebase should generate writers at all, since every public writer is a way to put an object into a state nobody validated.
## Methods that write methods Ruby objects expose state only through methods. Writing a reader and a writer by hand for every instance variable is repetitive, so `Module` provides three class-body helpers that generate them: | Call | Defines | Behaviour of the generated method | |---|---|---| | `attr_reader :target` | `target` | returns `@target`, or `nil` if it was never assigned | | `attr_writer :target` | `target=` | assigns its argument to `@target` | | `attr_accessor :target` | `target` and `target=` | both of the above | They are ordinary method calls that run while the class body is evaluated. Each accepts any number of names, as Symbols or Strings: ```ruby class Thermostat attr_reader :room attr_accessor :target, :mode end ``` ## What the generated code amounts to For a single name, `attr_accessor :target` behaves like this hand-written pair: ```ruby def target @target end def target=(value) @target = value end ``` The generated versions are not literally Ruby source: CRuby registers them as special method types that read or write the instance variable directly. For a caller there is no visible difference, and `instance_methods` lists them like any other method. Because they are ordinary methods, a subclass can override them, and a later `def target` in the same class replaces the generated reader. ## What they do not do - **They do not create the instance variable.** Defining the reader only teaches the class how to read `@target`. Until something assigns it, the reader returns `nil`. That is why the usual pattern pairs `attr_*` with assignments in `initialize`. - **They do not validate.** `attr_writer :target` stores whatever it receives, including `nil` or a String. If a value must be checked, write the writer by hand. - **They do not copy.** The reader hands back the very object stored in the variable, so a mutable object such as an Array can be changed by the caller. - **They do not accept every name.** A name must be usable as a local variable or constant name, so `attr_reader :heating?` raises `NameError`. ## The return value Since **Ruby 3.0**, each call returns an Array of the Symbols it defined: 1. `attr_reader :room` returns `[:room]`. 2. `attr_writer :target` returns `[:target=]`. 3. `attr_accessor :target, :mode` returns `[:target, :target=, :mode, :mode=]`. Before Ruby 3.0 these calls returned `nil`. The change exists so the result can be passed straight to another method that takes method names, most commonly a visibility modifier that makes the generated writer non-public. Older code that wrote the accessor and its visibility on two lines still works; the one-line form is simply available now. `attr` is a fourth, older spelling. `attr :room` is the same as `attr_reader :room`. The two-argument form `attr :room, true` still works but is deprecated, and warns in verbose mode when deprecation warnings are enabled. ## Visibility follows the section The generated methods take the default visibility in effect where the call appears. An `attr_reader` written below a bare `private` line produces a private reader, exactly as a `def` in that position would. ## When to write the methods by hand - The writer must **validate or convert** its argument, for example rejecting a target outside 5 to 30 degrees. - The reader must return a **copy** or a **computed** value rather than the stored object. - The method name should end in **`?`**, which the `attr_*` helpers cannot produce. For a plain field, the generated methods are shorter, conventional and read at a glance. RuboCop's `Style/TrivialAccessors` cop suggests replacing hand-written methods that only read or assign an instance variable of the same name with `attr_*` calls.
- In Ruby, why did the Ruby 3.0 change to attr_accessor's return value matter?Because the returned Array of Symbols can be fed to any method that takes method names. The common case is a visibility modifier wrapping the call, so a class can declare a reader public and its writer non-public on one line each, instead of repeating the names in a separate statement.
- In Ruby, does attr_accessor :target make @target appear in instance_variables right after Thermostat.new?No. `attr_accessor` only defines the `target` and `target=` methods. `@target` exists on an instance only after something assigns it, usually `initialize` or a call to the writer, so a fresh object lists only the variables its `initialize` actually set.
saying these in an interview costs you the question
- attr_accessor creates the instance variable and sets it to nil
- attr_accessor returns nil on Ruby 4.0
- attr_writer defines both the reader and the writer
- A generated writer validates the type of its argument
- A reader generated by attr_reader returns a copy of the stored object