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]({% post_url 2015-01-20-mastering-ruby-blocks-in-less-than-5-minutes %}) 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.