Ruby - DIY accessor methods

1. Ruby accessor methods

Ruby does not allow instance variables to be accessed outside of methods for that object.

class Person  
  def initialize(name)
    @name = name
    @age = 10
  end
end
p = Person.new('steve')
p.name  # =>  undefined method `name=' for #<Person ..>

This design ensures OOP encapsulation - an object’s data is protected from the outside world.
In some languages like Java, to access private instance variables, we must define getter/setter methods for each attribute. But in Ruby, we can generate getter/setter methods by using one of the methods in the Module#attr_* family.

class Person
  attr_reader :name, :age      # getter 
  attr_writer :name, :age      # setter
  # attr_accessor :name, :age  # getter + setter
  # other code ....
end
p = Person.new('steve')
p.name  # =>  steve
p.age = 20
p.age # => 20

It just takes one or two lines for all of the attributes. These methods are called Class Macros which are class methods only used when in a class definition.

2. DIY accessor methods

There are advanced ways to access instance variables, like instance_variable_get, instance_eval. For example:

p = Person.new('steve')
p.instance_variable_get(:@name)
p.instance_eval { @name }

Using some metaprogramming techniques, we can build attribute accessor methods by myself.

module GetterSetter
  def attr_getter(*attributes)
    attributes.each do |attribute|
      define_method attribute do
        instance_variable_get("@#{attribute}")
      end
    end
  end

  def attr_setter(*attributes)
    attributes.each do |attribute|
      define_method "#{attribute}=" do |value|
        instance_variable_set("@#{attribute}", value)
      end
    end
  end

  def attr_getter_and_setter(*attributes)
    attr_getter(*attributes)
    attr_setter(*attributes)
  end
end

For simplicity, I define getter/setter in separated module and include in Person class.

class Person
  extend GetterSetter
  
  def initialize(name)
    @name = name
    @age = 10
  end

  attr_getter :name, :age
  attr_setter :name, :age
  # or 
  # attr_getter_and_setter :name, :age
end

p = Person.new('Luke')
p.name = 'anakin'
p.name # => anakin

Summary

Class macros are one of Ruby's magic methods that help developers get rid of a lot of tedious, boilerplate methods. In rails, class macros are used a lot, for associations ( has_many, belongs_to ), validation, ...
You can learn more about Ruby class macros or other metaprogramming techniques in the Metaprogramming Ruby book ( https://pragprog.com/titles/ppmetr2/metaprogramming-ruby-2/)

3