Question

Ruby attr_accessor vs attr_reader vs attr_writer Explained

rubyattributesattr-accessor

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_reader creates a getter method
  • attr_writer creates a setter method
  • attr_accessor creates 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
  end

  def email=(value)
    @email = value
  end
end

The reason these helpers exist separately is not mainly performance. The main reason is encapsulation.

Encapsulation means an object controls how other code interacts with its internal data. Not every value should be freely readable and writable.

Why this matters

If you always use attr_accessor, every exposed attribute becomes both readable and changeable from outside the object. That may:

  • allow invalid state changes
  • reveal data that should stay private
  • make debugging harder because any code can modify the object
  • weaken the design of your class

Choosing between reader, writer, and accessor is a way of expressing intent:

  • attr_reader: others may see this value, but not change it directly
  • attr_writer: others may set this value, but not read it directly
  • attr_accessor: others may both read and change it

This makes your class API more deliberate and safer.

Mental Model

Think of an object like a building with doors.

  • attr_reader gives people a window to look through
  • attr_writer gives people a mail slot to put something in
  • attr_accessor gives 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.

Take Quiz

Syntax and Examples

Basic syntax

class Person
  attr_reader :name
  attr_writer :password
  attr_accessor :email
end

This creates these methods:

  • name
  • password=
  • email
  • email=

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.

Example: write-only attribute

class Account
  attr_writer :password
end

account = Account.new
account.password = "secret123"

account.password
# NoMethodError

Why use this?

A password may be something you want to set, but not expose for reading.

Example: read and write attribute

class Profile
  attr_accessor :nickname
end

profile = Profile.new
profile.nickname = "ruby_dev"
puts profile.nickname
# ruby_dev

Why use this?

For values that are safe and expected to change freely, attr_accessor is fine.

Multiple attributes

class User
  attr_reader :id, :created_at
  attr_accessor :name, :email
  attr_writer :password
end

This is common in real classes: different attributes have different access levels.

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

  1. Ruby reads attr_reader :title.

    • It creates a method called title.
    • That method returns @title.
  2. Ruby reads attr_accessor :price.

    • It creates a getter method price.
    • It also creates a setter method price=.
  3. Book.new("Ruby Basics", 20) runs initialize.

    • @title becomes "Ruby Basics"
    • @price becomes 20
  4. book.title calls the generated reader.

    • It returns @title
    • Output: Ruby Basics
  5. book.price calls the generated getter.

    • It returns @price
    • Output: 20
  6. book.price = 25 calls the generated setter.

    • It updates @price to 25
  7. book.price is called again.

    • It now returns 25

Important detail

book.title = "New Title" would fail because only a reader was created for title, not a writer.

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.

class AppConfig
  attr_accessor :host, :port
  attr_reader :connection_url
end

5. Domain rules

A business object may allow reading a value but only allow changes through special methods.

class BankAccount
  attr_reader :balance

  def deposit(amount)
    @balance += amount
  end
end

Here, balance should not be freely writable with balance = -1000.

Take Quiz

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
    @age = value
  end
end

This keeps write access but adds rules.

Read-only public API, private internal updates

class Session
  attr_reader :token

  def refresh_token
    @token = generate_token
  end

  private

  def generate_token
    "abc123"
  end
end

Other code can read token, but only the class itself changes it.

Narrow public interface

Well-designed classes expose only what other parts of the system actually need. This makes the code:

  • easier to understand
  • safer to refactor
  • less likely to be misused

Intention-revealing APIs

Using attr_reader instead of attr_accessor communicates that a value is not meant to be changed casually. That message helps teammates understand the class design quickly.

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:

  • name
  • name=

It does not make @name public.

Calling setters without self inside a class

Inside instance methods, this is a common bug:

class Person
  attr_accessor :name

  def rename(new_name)
    name = new_name
  end
end

This creates a local variable name instead of calling the setter.

Correct version

class Person
  attr_accessor :name

  def rename(new_name)
    self.name = new_name
  end
end

Using attr_writer without a real reason

Write-only attributes are less common. If code needs to set a value, ask whether it also needs to read it later. Use attr_writer only when hiding reads is intentional.

Forgetting that custom methods can replace helpers

If an attribute needs validation or transformation, you do not have to use the helper.

class Temperature
  def celsius=(value)
    raise ArgumentError, "too low" if value < -273.15
    @celsius = value
  end

  def celsius
    @celsius
  end
end

Attribute helpers are convenience tools, not mandatory patterns.

Comparisons

HelperCreates getter?Creates setter?Typical use
attr_readerYesNoRead-only public data
attr_writerNoYesWrite-only input
attr_accessorYesYesFully readable and writable data

attr_reader vs attr_accessor

  • Use attr_reader when a value should be visible but not freely changed.
  • Use attr_accessor when external code should both inspect and update it.

attr_writer vs custom setter method

  • Use attr_writer for a simple plain assignment.
  • Use a custom setter when you need validation, formatting, logging, or side effects.
class User
  def email=(value)
    raise ArgumentError, "invalid email" unless value.include?("@")
    @email = value.downcase
  end
end

Attribute helpers vs explicit methods

ApproachBest for
Attribute helperSimple getter/setter generation
Explicit method definitionsValidation, business rules, custom behavior

The main choice is not speed. It is how much control your class should have over its public interface.

Take Quiz

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 only
  • attr_writer → write only
  • attr_accessor → read and write

Use these rules

  • Use attr_reader for values others may inspect but should not modify directly.
  • Use attr_writer for values that should be set but not exposed.
  • Use attr_accessor only 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

self.name = "Alice"   # calls setter
name = "Alice"        # local variable assignment

When to avoid plain helpers

Write custom methods instead of helpers when you need:

  • validation
  • formatting
  • conversion
  • logging
  • business rules

Key idea

These helpers define methods, not public instance variables.

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 self.name = "Alice".

Can I use more than one attribute at once?

Yes.

attr_reader :id, :created_at
attr_accessor :name, :email
Take Quiz

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 BankAccount class with a readable balance
  • Initialize the account with a starting balance
  • Add a deposit method that increases the balance
  • Add a withdraw method that decreases the balance only if enough money exists
  • Prevent direct external assignment to balance
Take Quiz

Keep learning

Related questions

View more