For the past 8 months, I’ve been mentoring for The Firehose Project; an online Rails bootcamp.

What follows is a rudimentary introduction to leveraging instance variables in Ruby. Along the way, I’ll talk about my preferred class methodology as it relates to setting instance variable vs. calling setter methods.

# Example 1
class Book
  def initialize(a_title)
    @title = a_title
  end

  def title
    @title
  end
end

This can be condensed into the following:

# Example 2
class Book
  def initialize(a_title)
    @title = a_title
  end

  attr_reader :title
end

More and more I prefer to not set instance variables as part of initialization, but instead call setter methods (more on that later).

# Example 3
class Book
  def initialize(a_title)
    # Make sure to remember to use `self.title = a_title` instead of
    # `title = a_title`. If you use `title = a_title`, you will be setting the
    # local variable `title` instead of calling the instance method `#title=`
    # (and thus setting the instance variable `@title`)
    self.title = a_title
  end

  attr_reader :title

  def title=(a_title)
    @title = a_title
  end
end

This can be condensed into the following:

# Example 4
class Book
  def initialize(a_title)
    self.title = a_title
  end

  attr_reader :title
  attr_writer :title
end

And further into:

# Example 5
class Book
  def initialize(a_title)
    self.title = a_title
  end

  attr_accessor :title
end

Using attr_accessor and instance method setters on initialize is my preferred methodology (as in Example #5). Previously, I’ve used Example #2 as my defacto methodology. However, I’m less inclined to use that these days.

My reasoning for using a setter method is as follows:

  • I am encapsulating how the instance variable is ultimately set.
  • I am easing any work that may need to be done to extend the Book class.

Consider the following:

# Example 6
class FrenchBook < Book
  def title=(a_title)
    @title = translate_to_french(a_title)
  end
end

In using the setter method, the setter method is an inflection point – a place to extend and modify - in the code.

If I were setting the instance variable in the initialize method, my inflection point would be the initialize method. The following example illustrates that change.

# Example 7
class Book
  def initialize(a_title)
    @title = a_title
  end
  attr_reader :title
end

class FrenchBook < Book
  def initialize(a_title)
    @title = translate_to_french(a_title)
  end
end

The overall lines of code for both examples are the same. But consider what happens when we need to change the Bookclass.

# Example 8
class Book
  def initialize(a_title, an_author)
    # do stuff here
  end
end

The FrenchBook class of Example 7 would need to change.

Conclusion

Using attr_writer instead of setting an instance variable is a quick means of separating the concerns of your methods. Just as a class should have a single responsibility (i.e. a reason to change), so too should your methods.

By using a method to set your instance variable, you are making it easier for extension, modification, and overall maintenance.

What follows is my preferred class structure these days.

Example 9

class Book
  def initialize(a_title)
    self.title = a_title
  end

  attr_accessor :title
  private :title=
end

Outside of object instantiation, I don’t want to expose any means of updating the title. I want to protect from outside forces the mutability of the object’s state.