skip to content

Object Anatomy

How Ruby classes build and describe their instances: initialize and accessors, visibility, inheritance, class state, equality, Struct and Data, and copying. Interviewers probe them all.

on this pageshow

explore

questions

page 1 of 2

In Ruby, which methods do attr_reader, attr_writer and attr_accessor define, and what does each call return?

level: juniorimportance: must knowfreq 78%

answer

  1. name and name= methods
  2. reader returns @name, nil if unset
  3. no instance variable created
  4. returns an Array of Symbols
  5. return value changed in Ruby 3.0

basics

~10 s

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

The 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 lines
ruby
class 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 defined

go deeper

for a junior

Recall the three helpers and the methods each defines: name, name=, or both, reading and writing the matching instance variable.

for a middle

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.

for a senior

Show when generated methods are the wrong tool: validation in writers, copies or computed values in readers, and names ending in a question mark.

for a principal

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
open as a page

Inside a Ruby Thermostat method, why does target = 22 leave the target unchanged even though attr_accessor :target exists?

level: juniorimportance: must knowfreq 76%

basics

~10 s

Ruby parses a bare target = 22 as assignment to a new local variable, not a call to target=. Writer methods always need a receiver, so inside the class write self.target = 22.

open as a page

In Ruby, how do you define a class method such as Shape.build with def self., and how does it differ from an instance method?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Write def self.build inside the class body, where self is the class, and call it as Shape.build. Instances do not have it; they call self.class.build. Subclasses inherit it, and inside it self is whichever class received the call.

open as a page

In Ruby, what does Object#freeze prevent, and why can a frozen constant Hash still see its nested Array change?

level: juniorimportance: must knowfreq 64%

basics

~20 s

freeze stops changes to that one object's own state and returns the object; later writes raise FrozenError. It is shallow: objects it references stay mutable. Freezing a constant's value also does not stop the constant being reassigned, which only warns.

open as a page

In Ruby, what is the difference between ==, eql? and equal?, and why is 1 == 1.0 true while 1.eql?(1.0) is false?

level: juniorimportance: must knowfreq 82%

basics

~20 s

equal? asks whether both are the same object; == compares values and is overridden per class; eql? is the stricter hash-key equality. Numeric == compares across types, so 1 == 1.0, but eql? requires the same class.

open as a page

In Ruby, how do you make ElectricCar a subclass of Vehicle, override one of its methods, and still run Vehicle's version from the override?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Write class ElectricCar < Vehicle. ElectricCar then inherits Vehicle's methods and constants; defining a method with the same name overrides it, and calling super inside that override runs Vehicle's version and returns its value.

open as a page

In Ruby, what happens step by step when you call BankAccount.new("Ana", 100), and what does that call return?

level: juniorimportance: must knowfreq 72%

basics

~10 s

Class#new first calls allocate to get a blank BankAccount, then calls that object's private initialize with the same arguments and block, and finally returns the new object, whatever initialize itself returned.

open as a page

In Ruby 4.0, what does Struct.new return, and how do positional, keyword and keyword_init: construction behave?

level: juniorimportance: must knowfreq 58%

basics

~10 s

Struct.new returns a new class with a reader and a writer per member. Its instances take positional or, since Ruby 3.2, keyword arguments; omitted members are nil, and keyword_init: true makes construction keyword-only.

open as a page

In Ruby, how do is_a?, kind_of? and instance_of? differ when you check an object's class?

level: juniorimportance: must knowfreq 62%

basics

~20 s

is_a? and kind_of? are the same method: true when the class is the object's class, a superclass, or a module mixed into it. instance_of? is true only for the object's exact class, so subclasses and modules return false.

open as a page

In Ruby, which callers may invoke a public, a private and a protected method, and can a subclass call its parent's private methods?

level: juniorimportance: must knowfreq 80%

basics

~20 s

Public methods are callable by anyone. Private methods can be called only without an explicit receiver or with literal self. Protected methods accept an explicit receiver when the caller's self is a kind of the defining class. Subclasses can call inherited private methods.

open as a page

In Ruby, how does a @@class variable differ from a class-level @instance variable when Shape has subclasses such as Circle and Square?

level: middleimportance: must knowfreq 70%

basics

~20 s

A @@class variable is one storage slot shared by Shape and every subclass, so a write from Circle changes Shape's value. A class-level @ivar belongs to one class object only; Circle does not inherit it and reads nil.

open as a page

In Ruby, how do dup and clone differ for singleton methods and frozen state, and what does clone(freeze:) change?

level: middleimportance: must knowfreq 72%

basics

~20 s

Both make shallow copies. clone also copies the singleton class (singleton methods and extended modules) and the frozen state; dup copies neither, so it returns a plain, unfrozen copy. clone(freeze: true) or clone(freeze: false) sets the copy's frozen state explicitly.

open as a page

In Ruby, how do you make Point objects work as Hash keys and deduplicate with Array#uniq, and why must eql? and hash be overridden together?

level: middleimportance: must knowfreq 66%

basics

~20 s

Override == for value comparison, alias eql? to it, and define hash as [self.class, x, y].hash. Hash and uniq first group objects by hash, then confirm with eql?, so overriding only one leaves equal points as separate keys.

open as a page

In Ruby, what arguments and block does a bare super pass to the overridden method, and how do super() and super(a) differ from it?

level: middleimportance: must knowfreq 70%

basics

~20 s

A bare super re-sends all of the current method's parameters, using their current values, plus its block. super() sends no arguments and super(a) sends exactly a; both still pass the block unless you pass &nil or another block.

open as a page

In Ruby, how would you write an export method that accepts anything responding to each, and what happens when a caller passes something else?

level: middleimportance: must knowfreq 58%

basics

~20 s

Call rows.each without a class check, so Arrays, Sets, Hashes, Enumerators and any object defining each all work. Anything else fails with NoMethodError, such as undefined method 'each' for an instance of Integer, or for nil.

open as a page

How do you let Ruby Salary objects compare amounts in == without making amount public, and why does private fail there?

level: middleimportance: must knowfreq 62%

basics

~10 s

Declare the reader protected: protected :amount. Inside Salary#==, other.amount is then allowed because the caller is a Salary, while outside code gets NoMethodError. A private amount fails because other.amount uses an explicit receiver.

open as a page

In Ruby 4.0, how does a Data.define class differ from a Struct.new class, and which would you pick for a money value object?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Data.define (Ruby 3.2+) builds value classes whose instances are frozen, have no writers or Enumerable, and require every member. Struct.new builds mutable records with writers and optional members. Money is a value, so Data fits.

open as a page

In Ruby, why does editing a nested section of a dup-ed document template also change the original template?

level: juniorimportance: should knowfreq 62%

basics

~20 s

dup copies the template's instance variables, not the objects they reference, so copy and original share one sections Array and the same section objects. Mutating a shared section changes both; reassigning an attribute on the copy does not.

open as a page

In Ruby, what does reading an instance variable that was never assigned return, and how can that hide a typo?

level: juniorimportance: should knowfreq 52%

basics

~20 s

An instance variable that was never assigned reads as nil, with no exception and, since Ruby 3.0, no warning even under -w. A misspelled @balnce therefore yields nil and fails later, far from the typo.

open as a page

In Ruby, why does attr_reader :heating? raise, and how do you give a Thermostat a heating? predicate instead?

level: middleimportance: should knowfreq 40%

basics

~10 s

attr_* 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.

open as a page

In Ruby, how do you override initialize_copy so that dup and clone of a document template also copy its nested sections?

level: middleimportance: should knowfreq 38%

basics

~10 s

Define initialize_copy(source), call super, then replace the shared instance variables with copies, such as @sections = @sections.map(&:dup). dup reaches it through initialize_dup and clone through initialize_clone, after the ivars are copied; initialize never runs.

open as a page

In Ruby, how do <=> and Comparable give a Temperature class <, ==, between? and clamp, and what should <=> return for incomparable values?

level: middleimportance: should knowfreq 45%

basics

~20 s

Define <=> to return a negative number, zero or a positive number, or nil when the argument is not comparable, then include Comparable. It builds <, <=, ==, >=, >, between? and clamp from <=>.

open as a page

In Ruby, what is the superclass chain above an ordinary class like Vehicle, and when would you subclass BasicObject instead of Object?

level: middleimportance: should knowfreq 36%

basics

~20 s

Vehicle.superclass is Object, Object.superclass is BasicObject, and BasicObject.superclass is nil. Kernel is a module Object includes, not a class. Subclass BasicObject for a near-empty proxy or DSL object where method_missing should catch almost every call.

open as a page

In Ruby, what do BankAccount.class, Class.superclass and Module.superclass return, and what does that say about classes?

level: middleimportance: should knowfreq 45%

basics

~20 s

BankAccount.class is Class, Class.superclass is Module and Module.superclass is Object. Every class is an ordinary object, an instance of Class, and every class is also a Module, which is why classes hold methods and constants.

open as a page

In Ruby, when do two Struct or Data instances compare equal with ==, and why can identical-looking values still differ?

level: middleimportance: should knowfreq 40%

basics

~20 s

Struct#== and Data#== return true only when both objects have exactly the same class and every member is == to its counterpart. Two classes with the same members, or a subclass and its parent, never compare equal.

open as a page

In Ruby 4.0, why is OpenStruct discouraged, and what happens when a Bundler app requires ostruct without a Gemfile entry?

level: middleimportance: should knowfreq 45%

basics

~20 s

OpenStruct defines accessors per instance via method_missing and singleton methods: slow, keys can shadow built-in methods, typos return nil. Since Ruby 4.0 ostruct is a bundled gem, so under Bundler its require fails without a Gemfile entry.

open as a page

In Ruby, when is checking respond_to? before calling a method the right move, and what can respond_to? get wrong?

level: middleimportance: should knowfreq 45%

basics

~20 s

Use respond_to? when a capability is optional and you branch on it, like flushing only if an output supports flush. It ignores private and protected methods unless you pass true, and objects using method_missing report false without respond_to_missing?.

open as a page

In Ruby, how do a bare private section, private with a symbol list and private attr_reader differ, and what does a section miss?

level: middleimportance: should knowfreq 48%

basics

~20 s

A bare private makes later defs in that class body private; private :a, :b changes named existing methods; private attr_reader :x works since Ruby 3.0. A section does not affect def self.x class methods or other class bodies.

open as a page

A Ruby Thermostat exposes attr_reader :schedule over an internal Array; why can callers still corrupt it, and how do you stop them?

level: seniorimportance: should knowfreq 38%

basics

~20 s

attr_reader returns the stored Array itself, so a caller's schedule << entry mutates the Thermostat's own state. Return a copy from a hand-written reader, freeze what you hand out, or expose narrower methods instead of the collection.

open as a page

Why do experienced Ruby developers avoid @@class variables, and what would you use instead for per-class settings in a Shape hierarchy?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A @@ variable is one slot shared by a class and its whole subclass tree, so a subclass's write changes everyone's value, and load order can create conflicting copies. Use class-level instance variables behind def self. readers instead.

open as a page

showing 1–30 of 38