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.
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."
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:
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...
# 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.