A Ruby object has its methods public by default, but its data is private. So if you need to access the data, for either reading or writing, you need to make it public somehow.
And that is where the attr_reader
, attr_writer
, and attr_accessor
methods come in handy.
What is attr_reader?
Let’s take a look at a simple example of a Ruby class definition.
# person.rb
class Person
def initialize(name)
@name = name
end
end
john = Person.new("John")
puts john.name # => undefined method `name'
You cannot get the data (i.e. the name) from the person object because it’s private. So to make it publicly available, you need a method. Remember what I said earlier; methods in Ruby are public by default.
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
john = Person.new("John")
puts john.name # => John
By defining a name
method, you can now read the data. This kind of method is called a getter method.
But imagine having more than just the @name
variable. Maybe you want to have variables for first and last name, sex, age, and email. That would mean you would have to define methods like the one above for each variable. That would be horrible.
But as you know, Ruby makes your happiness a top priority, and so it gives you the attr_reader
method. It does exactly the same thing, only now you can shorten that code like so.
class Person
attr_reader :name
def initialize(name)
@name = name
end
end
john = Person.new("John")
puts john.name
And if you needed more methods, it just need to add them to the list, like this: attr_reader :fname, :lname, :age, :sex, :email
. Pretty cool.
What is attr_writer?
Let’s say instead of reading the data from an object, you want to change it. Again, because data is private, and methods are public, you need to define a method. Only this time, the method doesn’t read the data, it writes data. That kind of method is called a setter method.
class Person
attr_reader :name
def initialize(name)
@name = name
end
def name=(name)
@name = name
end
end
john = Person.new("John")
john.name = "Jim"
puts john.name # => Jim
To define a setter method, you add the = sign at the end of the method’s name. So now you have the method name= that can be used to change the data inside the object.
But Ruby has a short version for that too. It is called attr_writer
. And this is how looks like.
class Person
attr_reader :name
attr_writer :name
def initialize(name)
@name = name
end
end
john = Person.new("John")
john.name = "Jim"
puts john.name # => Jim
You’ve probably noticed something there. As you grow your list of methods, and especially if you need both the getter and the setter, something funny starts happening. You end up with two lines of code that look very much alike. That’s not good.
class Person
attr_reader :name, :age, :sex, :email
attr_writer :name, :age, :sex, :email
def initialize(name)
@name = name
end
end
It makes your code look ugly and it introduces maintenance overhead (i.e. whenever you create a new method, you need to add its name to both lists).
But, as you can expect, Ruby has a solution to that problem too. It is called attr_accessor
.
What is attr_accessor?
Basically attr_accessor
is a shortcut for when you need both attr_reader
and attr_writer
. It squashes down those two lines into one. Like so.
class Person
attr_accessor :name, :age, :sex, :email
def initialize(name)
@name = name
end
end
That is the idiomatic way of doing it. But you don’t have to do it like that. Here’s another one way you could acomplish the same thing.
Define mehtod
The define_method
method is used to define methods dynamically in Ruby. It takes the name of the method you want to define as its first argument, and a block for the body of the method.
class Person
def initialize(name)
@name = name
end
def self.define_attr(attr)
define_method(attr) do
instance_variable_get("@#{attr}")
end
define_method("#{attr}=") do |val|
instance_variable_set("@#{attr}", val)
end
end
end
john = Person.new("John")
Person.define_attr(:name)
john.name = "Jim"
puts john.name # => Jim
Ruby has many ways of achieving the same goal but it’s a good idea to follow the idiomatic way.
There is also a significant performance advantage to using the attr_reader
, attr_writer
, and attr_accessor
methods. Check out Aaron Patterson’s talk to see why.