Ruby's attr_accessor, attr_reader and attr_writer

Ruby's attr_accessor, attr_reader and attr_writer

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.

If you liked this article, please take a moment and say thanks by sharing it on your favorite social media channel.