Ruby: Making sen(d)se of send()
In programming, good code is the code that gets the job done. Great code is the code that gets other other jobs done. If the Golden rule of Ruby is “Don’t Repeat Yourself”, the Silver rule might be to “Be As Abstract As Possible.”
The send() method acts on an object to invoke the method supplied in send()’s arguments. In layman’s terms, you use the send method to call another method on the same object. You might be wondering, “Isn’t the send method just a middleman? Why can’t I just call the method on the object directly without the send method?” We’ll get to that later.
The send() method always takes at least, but is not limited to, 1 argument. This means you could could call on the send() method with 1 argument or even 10 arguments. These arguments can be given as either a symbol or a string.
sample_object = Object.new
sample_object.send(:class) == sample_object.send("class")
The first argument supplied is always the method you want to call on the original object. Think of this as the secondary method, which in the case above, is the .class method. This secondary method is then called on, or “sent” to, the original object. The result would be the same if you called on the secondary method directly on the object.
# is equivalent to
Every argument after the first one will be the arguments supplied to secondary method. In the following example, we set a variable “number” equal to a value of 1 and send the “+” method to “number” with an argument of 2. This is equivalent to doing “number.+ 2” which is just “number + 2”.
number = 1
number + 2
The basic syntax of the send method can be described as:
It acts on some_object, calls some_method on that object, with a number of arguments supplied to that some_method.
The asterisk is the splat operator, which basically means any number of arguments, from 0 to any number.
Now you’re probably wondering, when is this ever useful. One example is instantiating three classes with different attributes. In our following example, we have 3 classes, an Author class, an Artist class, and a Friend class.
@pen_name = pen_name
@stage_name = stage_name
@nickname = nickname
These initialize methods seem pretty similar and they are! They’re all just taking an argument and setting it equal to an attribute of the current class. This method could be abstracted and placed in a superclass which these 3 classes will inherit from. Let’s call this class Person.
attributes.each do |key, value|
class Actor < Person
class Author < Person
class Friend < Person
The initialize method is abstracted into a super class. In this abstracted initialize method, you’re receiving a hash as an argument, which we called attributes. The keys in this hash are the attributes you want to assign to the classes (in Actor, it would be stage_name) and the values are what you want that attribute to equal. We iterate over attributes and for each key-value pair, we’re using send() to assign a value to each attribute listed in the hash.
We could make instances of the each of the classes like so:
actor = Actor.new(stage_name: "Martin Sheen")
author = Author.new(pen_name: "Mark Twain")
friend = Friend.new(nickname: "Crazy Ian")
=> "Martin Sheen"
=> "Mark Twain"
=> "Crazy Ian"
Isn’t that cool!? You could make new classes that inherit from the Person class and you just need to include an attr_accessor with the attribute you want to use without writing the initialize method in the class. The MVP here is the send method. Each time you’re using the initialize method for a different class, you’re calling on a different setter method. This shows you how versatile send() is in making abstract code.
# send called on these 3 methods!
actor.stage_name= "Martin Sheen"
author.pen_name= "Mark Twain"
friend.nickname= "Crazy Ian"
Essentially, send() can be translated as:
actor.send(stage_name=, "Martin Sheen")