Attributes (attr_accessor)

Reviewed & published by Brayan K

Ruby is a dynamic, beginner-friendly programming language, and attributes are how an object exposes its data — attr_accessor generates the getter and setter methods so you don't write them by hand.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll understand getters and setters, use attr_reader/writer/accessor, and write custom accessors with validation.

What You'll Learn in This Lesson

1️⃣ The Manual Way: Getters and Setters

Instance variables are private to the object. To expose one, you write a getter (a method that returns it) and a setter (a method named with a trailing = that assigns it). Seeing this by hand makes the shortcut obvious.

# Instance variables are PRIVATE. To read or change them from outside,
# you normally write getter and setter methods by hand:
class PersonManual
  def initialize(name)
    @name = name
  end

  def name          # getter: returns the value
    @name
  end

  def name=(value)  # setter: the = makes it assignable
    @name = value
  end
end

p1 = PersonManual.new("Ada")
puts p1.name        # -> Ada       (calls the getter)
p1.name = "Grace"   # calls the setter method name=
puts p1.name        # -> Grace

2️⃣ attr_accessor, attr_reader, attr_writer

attr_accessor :name generates both the getter and the setter automatically. Use attr_reader for read-only data (a getter only) and attr_writer for write-only. You list the attributes as symbols.

# attr_accessor writes those getter AND setter methods for you.
class Person
  attr_accessor :name, :age   # read + write for both
  attr_reader   :id           # read-only (getter only)

  def initialize(name, age, id)
    @name = name
    @age = age
    @id = id
  end
end

p = Person.new("Ada", 36, 1001)
puts p.name          # -> Ada   (getter generated by attr_accessor)
p.age = 37           # setter generated by attr_accessor
puts p.age           # -> 37
puts p.id            # -> 1001  (attr_reader: readable)
# p.id = 2           # would raise NoMethodError — no setter exists

# attr_writer would give a setter only (write-only), e.g. attr_writer :password

Your turn. Add the right accessors to a Product class so its name is editable but its SKU is read-only. Complete the TODOs, then run it.

# 🎯 YOUR TURN — replace each TODO, then run with: ruby product.rb

class Product
  # 1) Make :name readable AND writable, and :sku read-only
  attr_accessor :name, :price    # 👉 TODO: name and price read/write
  attr_reader :sku               # 👉 TODO: sku read-only

  def initialize(name, price, sku)
    @name = name
    @price = price
    @sku = sku
  end

  def discount(percent)
    @price = (@price * (1 - percent / 100.0)).round(2)
  end
end

item = Product.new("Mug", 12.0, "MUG-01")
item.name = "Travel Mug"        # uses the generated setter
item.discount(25)               # apply 25% off
puts "#{item.name} (#{item.sku}): $#{item.price}"

# ✅ Expected output:
#    Travel Mug (MUG-01): $9.0

Mini-Challenge: A Guarded Setter

Write a custom temperature= setter that validates its input — the real payoff of writing accessors yourself. Run with ruby thermostat.rb.

# 🎯 MINI-CHALLENGE: a Thermostat with a guarded setter.
# Save as thermostat.rb and run with: ruby thermostat.rb
#
# 1. Define a class Thermostat with attr_reader :temperature.
# 2. In initialize, set @temperature to a starting value (e.g. 20).
# 3. Write your OWN setter "temperature=" that refuses values below 0,
#    keeping the old value and printing "Too cold!" instead.
# 4. Test it: set a valid value, then try -5, and print the temperature.
#
# ✅ Example output:
#    Too cold!
#    Temperature: 22

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Attributes

ConceptRuby Syntax
Read + writeattr_accessor :name
Read onlyattr_reader :id
Write onlyattr_writer :pw
Custom getterdef name; @name; end
Custom setterdef name=(v)
Set inside classself.name = v

🎉 Lesson Complete!

Practice quiz

What does attr_accessor :name generate?

  • Only a getter named name
  • Only a setter named name=
  • Both a getter (name) and a setter (name=)
  • An instance variable named name

Answer: Both a getter (name) and a setter (name=). attr_accessor writes both the getter and the setter for you at class-definition time.

Which macro creates a READ-ONLY attribute (getter only)?

  • attr_reader
  • attr_writer
  • attr_accessor
  • attr_const

Answer: attr_reader. attr_reader generates just the getter, so the value can be read but not written from outside.

If a class uses attr_reader :id, what happens when you call obj.id = 5?

  • It silently does nothing
  • It sets @id to 5
  • It creates a new attribute
  • It raises NoMethodError because no setter exists

Answer: It raises NoMethodError because no setter exists. attr_reader makes no setter, so assigning raises NoMethodError (undefined method 'id=').

How is a setter method named in Ruby?

  • set_name
  • name= (with a trailing equals sign)
  • name!
  • @name

Answer: name= (with a trailing equals sign). Ruby routes obj.name = x to the method name=(x); the trailing = is what makes it a setter.

You must pass attributes to attr_accessor as:

  • Symbols, like :name
  • Strings, like "name"
  • Instance variables, like @name
  • Local variables

Answer: Symbols, like :name. Accessor macros take symbols: attr_accessor :name, :age.

Inside an instance method, how do you correctly call the generated setter for name?

  • name = x
  • @name = x only
  • self.name = x
  • set name x

Answer: self.name = x. A bare name = x makes a local variable; self.name = x routes through the setter method.

attr_writer :password generates which method(s)?

  • A getter only
  • A setter only (write-only)
  • Both getter and setter
  • Neither

Answer: A setter only (write-only). attr_writer makes only the setter, useful for write-only values you don't want to expose.

Why would you hand-write a setter instead of using attr_accessor?

  • It runs faster
  • attr_accessor is deprecated
  • Hand-written setters are required by Ruby
  • To add validation or side effects when the value is set

Answer: To add validation or side effects when the value is set. A custom value= setter lets you validate or transform input — the payoff of writing it yourself.

Instance variables (the @-prefixed ones) are, by default:

  • Public and readable from anywhere
  • Private to the object — exposed only through methods
  • Shared across all instances
  • Automatically frozen

Answer: Private to the object — exposed only through methods. Instance variables are private; getters and setters are how you expose them to the outside.

Best practice for choosing accessors is usually to:

  • Always use attr_accessor
  • Never use accessor macros
  • Start with the least access (e.g. attr_reader) and widen only when needed
  • Use attr_writer for everything

Answer: Start with the least access (e.g. attr_reader) and widen only when needed. Default to read-only and add a setter only when callers truly need to change the value.

Continue this course

Frequently asked questions

What does attr_accessor actually do?

attr_accessor is a shortcut that writes getter and setter methods for you at the moment the class is defined. attr_accessor :name generates a name method that returns @name and a name= method that assigns it, so you never write that boilerplate by hand. It's not magic syntax — it's a regular method (a class macro) that defines other methods. attr_reader generates only the getter, and attr_writer only the setter.

When should I use attr_reader, attr_writer, or attr_accessor?

Choose based on the access you want to allow. Use attr_reader for values that should be readable but never changed from outside, like an id or a creation timestamp. Use attr_writer (rarely) for write-only values like a password you set but don't expose. Use attr_accessor when callers may both read and update the value, like a person's name. Starting with the least access and widening only when needed keeps your objects well-encapsulated.

Why is the setter method written with an = in its name?

Ruby has special syntax support for method names ending in =. When you write person.name = "Bo", Ruby actually calls the method name=("Bo"). That's why a setter is defined as def name=(value). This is how Ruby makes attribute assignment look like simple variable assignment while still routing through a method — which means you can add validation or side effects inside your own custom setter.

Can I write my own getter or setter instead of using attr_accessor?

Absolutely, and you should whenever you need logic. attr_accessor only gives you a plain pass-through. If you want a setter that validates input (rejecting a negative temperature, say) or a getter that computes or formats a value, write the method yourself — def temperature=(value); ... end. You can even mix the two: use attr_reader for the getter and hand-write just the setter. Custom accessors are where encapsulation earns its keep.