attr_accessor, attr_reader, and attr_writer
Ruby's shortcut for exposing instance variables, and why you should still choose carefully between the three.
2 min read
The classes lesson mentioned that instance variables are private by default, and that exposing one means writing a method. attr_accessor and its siblings write those methods for you.
The manual version
class Book
def initialize(title, author)
@title = title
@author = author
end
def title
@title
end
def title=(new_title)
@title = new_title
end
def author
@author
end
endtitle reads @title; title= (a method whose name ends in =) lets you write book.title = "New Title" and has that translated into a call to the title= method behind the scenes. This is boilerplate that scales badly once a class has five or ten fields.
attr_accessor, attr_reader, attr_writer
class Book
attr_accessor :title # generates both a reader and a writer
attr_reader :author # generates only a reader (read-only from outside)
attr_writer :internal_id # generates only a writer (write-only, rare)
def initialize(title, author)
@title = title
@author = author
end
end
book = Book.new("Dune", "Frank Herbert")
book.title # "Dune"
book.title = "Dune (Deluxe Edition)" # works -- accessor
book.author # "Frank Herbert"
book.author = "Someone Else" # NoMethodError -- no writer generatedEach of these is a single line that expands into the reader and/or writer method(s) you'd otherwise write by hand. You can pass multiple symbols to one call:
attr_accessor :title, :author, :isbnChoosing which one to use
Default to attr_reader unless external code genuinely needs to reassign the field. Exposing a mutable attr_accessor for everything is a common beginner habit that quietly breaks encapsulation — any external code can reach in and change an object's state directly, bypassing any validation or side effects your own methods might need to run.
class BankAccount
attr_reader :balance # read-only from outside -- correct choice
def initialize(balance)
@balance = balance
end
def deposit(amount)
raise ArgumentError, "amount must be positive" if amount <= 0
@balance += amount
end
endIf balance had attr_accessor instead, any code could do account.balance = -1000 directly, skipping the validation that deposit enforces. That's exactly the kind of bug encapsulation exists to prevent.
These aren't magic — you can still write custom logic
If a field needs validation or computed behavior beyond a plain get/set, write the method by hand instead of (or alongside) an accessor:
class Book
attr_reader :title
def title=(new_title)
raise ArgumentError, "title can't be blank" if new_title.strip.empty?
@title = new_title
end
endattr_accessor is a convenience for the common case, not a requirement — reach for a hand-written method the moment "just get and set the value" isn't the whole story.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.