Reason #45 • February 14th, 2026

Easy getters and setters

I don't always write classes, but when I do, it's nice to have a convenient way to define getters and setters for instance variables. In Ruby, you can use the attr_reader, attr_writer, and attr_accessor class methods to quickly create getter and setter methods for instance variables.

Ruby
class Person
  attr_accessor :name # creates both getter and setter for @name
  attr_reader :birthdate # creates a getter for @birthdate
  attr_writer :description # creates a setter for @description

  def initialize(name, birthdate, description = nil)
    @name = name
    @birthdate = birthdate
    @description = description
  end

  def description
    @description || "No description provided."
  end
end

person = Person.new("Alice", Time.parse("1990-01-01"))
puts person.name  # => "Alice"
puts person.birthdate # => 1990-01-01 00:00:00 +0000
puts person.description # => "No description provided."

person.name = "Bob"
puts person.name  # => "Bob"

person.description = "A software developer."
puts person.description # => "A software developer."
      
JavaScript
class Person {
  constructor(name, birthdate, description = null) {
    this._name = name;
    this._birthdate = birthdate;
    this._description = description;
  }

  get name() {
    return this._name;
  }

  set name(newName) {
    this._name = newName;
  }

  get birthdate() {
    return this._birthdate;
  }

  get description() {
    return this._description || "No description provided.";
  }

  set description(newDescription) {
    this._description = newDescription;
  }
}
const person = new Person("Alice", new Date("1990-01-01"));
console.log(person.name); // => "Alice"
console.log(person.birthdate);  // => 1990-01-01T00:00:00.000Z
console.log(person.description); // => "No description provided."

person.name = "Bob";
console.log(person.name); // => "Bob"

person.description = "A software developer.";
console.log(person.description); // => "A software developer."
      

Concise and clear, isn't it?

Under the hood

If you're new to Ruby, you may think that attr_accessor and friends are some kind of special syntax available within class definitions. In reality, they are just regular class methods that define instance methods when called:

Ruby
Person.class
# => Class

Class.instance_methods.grep(/attr_/)
# => [:attr_accessor, :attr_reader, :attr_writer]

Person.method(:attr_accessor)
# => <Method: #<Class:Person>(Module)#attr_accessor(*)>

Person.method(:attr_reader)
# => <Method: #<Class:Person>(Module)#attr_reader(*)>

Person.method(:attr_writer)
# => <Method: #<Class:Person>(Module)#attr_writer(*)>
      

But why is Module the receiver of these methods (seen within parentheses in the last three examples above)? We have to go deeper...

Ruby
# Passing false to #instance_methods prevents including inherited methods
Class.instance_methods(false).grep(/attr_/)
# => []
# Apparently these methods are defined further up the inheritance chain

Class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]
# Aha, Module is a superclass of Class

Module.instance_methods(false).grep(/attr_/)
# => [:attr_accessor, :attr_reader, :attr_writer]
      

So when we define a class, the receiver of attr_accessor and friends is actually the class itself, which is an instance of Class. Since Class inherits from Module, it has access to its methods, including attr_accessor, attr_reader, and attr_writer.

Some people may describe methods like these as "macros" or "meta-programming", but I personally prefer to demystify them to remind us that Ruby is fundamentally about messages getting passed to objects. Once you understand that every message has an object as a receiver, and the classes themselves are objects, the whole thing turns from "magic" into a beautifully consistent object-oriented design.

History

The attr_* methods have been part of Ruby since its inception.

It's hard to pinpoint an obvious source of inspiration. But the CLOS (Common Lisp Object System) slot accessors share a curiously similar naming convention. CLOS allows defining classes with "slots" (similar to instance variables) and provides an API using :reader, :writer and :accessor to assign getter and setter methods to those slots. Though it doesn't actually generate these methods for you.