Creating Your Own attr_reader and attr-writer

Have you ever wondered how you would create your own Ruby attr_reader and attr_writer.

Just so we are on the same page.

class Cat
 attr_reader :name
 attr_writer :name

 def initialize(<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="">name)
  @name</span></span></span></span> = name
 end
end

cat = Cat.new("Tobias")
cat.name #=> "Tobias"

cat.name #= "New Tobias"
cat.name #=> "New Tobias" 

attr_reader in the Cat class above is the same as if we create a method inside of it that the object’s the attribute name.

and attr_writer, as you have guessed but now allows the attribute to be written or assigned

class Cat

 def initialize(name)
  @name = name
 end

 def name
  @name
 end

 def name=(value)
  @name = value
 end
end

Build Your Own

Because attr_reader and attr_writer are pieces of code that creates code, we’ll have to enter the realm of metaprogramming.

For attr_reader, we will create an instance method that return its corresponding instance variable. And for attr_writer, we will code an instance method that assigns a value to an instance variable and returns it.

module Attrs
 
 def my_attr_reader(attr_name)
  define_method(attr_name) do 
   instance_variable_get("@"+attr_name.to_s)
  end
 end

 def my_attr_writer(attr_name)
  define_method("#{attr_name}=") do |value|
   instance_variable_set(“@“+attr_name.to_s, value)
  end
 end
end

In the module above I am using Ruby methods that let me define a method(define_method), get an instance variable(instance_variable_get) and set it(instance_variable_set). Don’t you love Ruby?

class Cat
 extend Attrs

 my_attr_reader :name
 my_attr_writer :name

 def initialize(name)
  @name = name
 end
end

cat = Cat.new("Tobias")
cat.name #=> "Tobias"

cat.name = "New Tobias"
cat.name #=> "New Tobias"

Voilá

Notice that I am using extend rather than include with the module Attrs. This is so my_attr_reader and my_attr_writer can become class methods.

Also, these are defining my_attr_reader and my_attr_writer once. In order to define multiple readers and writers at once like attr_reader and attr_writer you can do

module Attrs
 
def my_attr_reader(*attr_names)
 attr_names.each do |attr_name|
  define_method(attr_name) do
   instance_variable_get("@"+attr_name.to_s)
  end
 end
end

 def my_attr_writer(*attr_names)
  attr_names.each do |attr_name|
   define_method("#{attr_name}=") do |value|
    instance_variable_set("@"+attr_name.to_s, value)
   end
  end
 end
end

*attr_names lets you pass in zero or more attribute names. For each of those, a corresponding method is defined.

In case you are curious, the actual attr_reader and attr_writer in Ruby are implemented with functions written in C. When we simulate this behavior with our own Ruby methods we don’t get the advantage of these C functions.

And that is how you can replicate Ruby attr_reader and attr_writer with metaprogramming.
We used define_method, instance_variable_get, and instance_variable_set to define a method on a caller, get and set an instance variable.

This is just the tip of the metaprogramming iceberg.

Happy metaprogramming!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s