Ruby’s Send Method — Why It’s Important.
While creating COVID Stats CLI there were a number of obstacles restricting me from creating a dynamic and flexible application. Most notably was how to deal with creating and managing attribute readers and writers.
Due to the nature of the API I’m using I needed to have access to many different key/value pairs.
Heres an example of an array with hashes inside it. Iteration was needed over the array in order to create a new instance of my Country class from each hash.
[{"country" => "USA", "cases" => 1234},
{"country" => "Norway", "cases" => 450}]
I also needed to assign attribute readers and writers for each key/value pair in order that I could access them from outside the Country class. I could have done this by manually defining each attribute reader/writer using Ruby’s attr_accessor.
For example:
attr_accessor :country, :cases
This, however, would be tedious given that there are many key/value pairs and they are subject to change on the API side of things. So, this is where the–
send
– method comes into play. It allows me to dynamically and at mass, assign all the attr_accessor’s for each key/value pair defined in each hash.
What does send do?
In the simple form send invokes a method.
For example:
class Name
def say_hi_to(name)
"Hi #{name}"
end
endexample = Name.new
example.send :say_hi_to, "Yehuda"=> "Hi Yehuda"
Now that example wasn’t so special because we could have just done this —
class Name
def say_hi_to(name)
"Hi #{name}"
end
endexample = Name.new
example.say_hi_to("Yehuda")=> "Hi Yehuda"
The send method stands out when we don’t yet know the name of a variable. Under the hood of attr_accessor we are creating an attribute reader and an attribute writer, normally created like —
For example:
# attribute readerdef name
@name
end# attribute writerdef name=(value)
@name = value
end
attr_accessor is just a ruby method for accomplishing the above snippet.
attr_accessor :name
So we can use this method with send to iterate over our key/value pairs that are being passed into our initialize method upon the creation of a new instance in our Class.
For example:
attributes = {"country" => "USA", "cases" => 1234}class Country
def initialize(attributes)
attributes.each do |key, value|
self.class.attr_accessor(key)
self.send(("#{key}="), value)
end
end
end
If we create a new instance of our Country class –
new_country = Country.new(attributes)
=> #<Country:0x00007fcd178b4d68 @country="USA", @cases=1234>
– and then call our new class instance with our instance variable after it, we will be able to read that data.
new_country.country
=> "USA"
In Conclusion
Now we see the power of send. Since we are iterating over our attributes hash and using the send method on each pass to set the attr_accessor’s we end up with the reader’s and writer’s that we require. No more, no less.