Question
In Ruby, instance variables can be exposed using helper methods such as:
attr_accessor :var
attr_reader :var
attr_writer :var
Why would you choose attr_reader or attr_writer instead of simply using attr_accessor everywhere?
Is there any practical reason, such as performance, or are these alternatives mainly provided for design and code-organization purposes?
Short Answer
By the end of this page, you will understand what attr_reader, attr_writer, and attr_accessor do in Ruby, why they exist as separate tools, and how they help control access to an object's internal state. You will also see when to expose read-only, write-only, or read-write behavior in real Ruby code.
Concept
In Ruby, instance variables like @name belong to a specific object. By default, code outside the object cannot directly read or write those variables using regular method calls. Ruby provides attribute helpers to define methods for controlled access:
attr_readercreates a getter methodattr_writercreates a setter methodattr_accessorcreates both getter and setter methods
For example:
class User
attr_reader :name
attr_writer :password
attr_accessor :email
end
This is roughly equivalent to writing:
class User
def name
@name
end
def password=(value)
@password = value
end
def email
@email
()
= value
Mental Model
Think of an object like a building with doors.
attr_readergives people a window to look throughattr_writergives people a mail slot to put something inattr_accessorgives people both a window and a door
If every room had an open door, anyone could walk in and rearrange everything. That is convenient, but unsafe. Good class design means deciding which openings should exist.
Another way to think about it:
- A reader says, "You may inspect this"
- A writer says, "You may provide this"
- An accessor says, "You may inspect and change this"
The choice is about control, not just convenience.
Syntax and Examples
Basic syntax
class Person
attr_reader :name
attr_writer :password
attr_accessor :email
end
This creates these methods:
namepassword=emailemail=
Example: read-only attribute
class Product
attr_reader :id
def initialize(id)
@id = id
end
end
product = Product.new(101)
puts product.id
# 101
product.id = 200
# NoMethodError
Why use this?
An ID often should not change after an object is created. attr_reader lets other code view it without modifying it.
Step by Step Execution
Consider this class:
class Book
attr_reader :title
attr_accessor :price
def initialize(title, price)
@title = title
@price = price
end
end
book = Book.new("Ruby Basics", 20)
puts book.title
puts book.price
book.price = 25
puts book.price
Step-by-step
-
Ruby reads
attr_reader :title.- It creates a method called
title. - That method returns
@title.
- It creates a method called
-
Ruby reads
attr_accessor :price.- It creates a getter method
price. - It also creates a setter method
price=.
- It creates a getter method
-
Book.new("Ruby Basics", 20)runsinitialize.
Real World Use Cases
1. Read-only IDs and timestamps
In many applications, values such as these should be visible but not directly changed:
- database IDs
- creation timestamps
- generated tokens
class Order
attr_reader :id, :created_at
end
2. Sensitive write-only input
Some values are set from outside but should not be readable later in plain form:
class User
attr_writer :password
end
In real apps, the writer often hashes the password instead of storing raw text.
3. Editable profile fields
Some fields are naturally both readable and writable:
class Profile
attr_accessor :display_name, :bio
end
4. Configuration objects
A class may expose certain settings for modification while keeping internal derived values read-only.
,
Real Codebase Usage
In real Ruby codebases, developers usually avoid exposing everything with attr_accessor unless it is truly appropriate.
Common patterns
Guarding important state
class Invoice
attr_reader :status
def mark_paid
@status = :paid
end
end
This prevents outside code from doing:
invoice.status = :paid
Instead, the class controls how status changes happen.
Validation in custom setters
Sometimes developers start with attr_accessor, then replace it with a custom writer.
class Person
attr_reader :age
def age=(value)
raise ArgumentError, "age must be non-negative" if value < 0
= value
Common Mistakes
Using attr_accessor for everything
This is the most common mistake.
class User
attr_accessor :admin, :id, :created_at
end
This allows outside code to do things like:
user.admin = true
user.id = 999
That may be dangerous or invalid.
Better
class User
attr_reader :id, :created_at, :admin
end
Then provide controlled methods if needed.
Thinking these expose variables directly
These helpers create methods, not direct variable access syntax.
class Item
attr_accessor :name
end
This defines:
Comparisons
| Helper | Creates getter? | Creates setter? | Typical use |
|---|---|---|---|
attr_reader | Yes | No | Read-only public data |
attr_writer | No | Yes | Write-only input |
attr_accessor | Yes | Yes | Fully readable and writable data |
attr_reader vs attr_accessor
- Use
attr_readerwhen a value should be visible but not freely changed. - Use
attr_accessorwhen external code should both inspect and update it.
attr_writer vs custom setter method
Cheat Sheet
attr_reader :name # creates name
attr_writer :name # creates name=
attr_accessor :name # creates name and name=
What each one does
attr_reader→ read onlyattr_writer→ write onlyattr_accessor→ read and write
Use these rules
- Use
attr_readerfor values others may inspect but should not modify directly. - Use
attr_writerfor values that should be set but not exposed. - Use
attr_accessoronly when both reading and writing are truly appropriate.
Common example
class User
attr_reader :id
attr_accessor :name
attr_writer :password
end
Important setter rule inside instance methods
FAQ
Why not just use attr_accessor for every attribute?
Because it exposes both reading and writing. That can make your objects too easy to misuse and can break encapsulation.
Is there a performance difference between attr_reader, attr_writer, and attr_accessor?
In normal Ruby code, performance is not the reason to choose between them. The choice is mainly about API design and access control.
When should I use attr_reader in Ruby?
Use it when outside code should be able to see a value but should not directly change it, such as IDs, timestamps, or computed state.
When should I use attr_writer in Ruby?
Use it when a value should be set from outside but should not be readable directly, such as sensitive input like a password.
Can I replace attr_accessor with custom methods later?
Yes. This is very common when you later need validation or business logic.
Does attr_accessor make instance variables public?
No. It creates public getter and setter methods. The instance variables themselves are still internal to the object.
Why does name = "Alice" sometimes not call the setter?
Inside an instance method, Ruby treats that as local variable assignment unless you write .
Mini Project
Description
Build a small BankAccount class that demonstrates why attr_reader is often safer than attr_accessor. In a real banking system, code should be able to read the balance, but it should not be able to set the balance directly to any arbitrary number. Instead, updates should happen through controlled methods like deposit and withdraw.
Goal
Create a Ruby class where the account balance can be read publicly, but changed only through methods that enforce simple rules.
Requirements
- Create a
BankAccountclass with a readablebalance - Initialize the account with a starting balance
- Add a
depositmethod that increases the balance - Add a
withdrawmethod that decreases the balance only if enough money exists - Prevent direct external assignment to
balance
Keep learning
Related questions
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.
Difference Between require and include in Ruby
Learn the difference between require and include in Ruby, when to load a file, and when to mix module methods into a class.
Fixing Ruby Gem Native Extension Errors: mkmf.rb Can't Find Header Files for Ruby
Learn why Ruby gem installs fail with missing ruby.h, how native extensions work, and how to fix header file errors on Linux servers.