How .send() works

The story of #send

Let’s get meta for a minute. Remember the movie ‘Back to the Future’? Of course you do (and if you haven’t seen it, well, that deserves a whole other article). In the movie, Marty travels back in time and nearly alters the course of history when he interferes with his parents meeting and falling in love. His actions threaten his very existence, disrupting the time space continuum and life as he knows it.

This is of course fiction, but the idea of one action directly influencing another linked action is all too real.

Take the #send method, for instance. It’s a part of Ruby metaprogramming: a feature in which programs are written to write or manipulate other programs. Metaprogramming allows programmers to spend less time writing individual lines of code by having a method do it instead. 
 An example of this is using #attr_accessor in a new class instead of defining setter and getter instance methods over and over. #send is metaprogramming too. It allows you to manipulate a class with methods defined elsewhere in the code. You can use it to test private methods on a class, too. So meta, right?


How does it work? Let’s say you have defined a class, Car, below:

class Car
attr_accessor :engine
def initialize(engine)
@engine = engine

It initializes with an engine attribute. We can create an instance of Car called delorean:

delorean ="flux capacitor")

Let’s leave our delorean parked for a moment and define two methods, outside of the Car class, to demonstrate how #send will work here:

def drive(name)
"Vroom vroom! #{name} is driving around in a regular old car, no big deal."
def time_travel(name)
"SPARKS! FIRE! OMG, #{name} has traveled back in time to 1955! His mom has the hots for him! Ew!"

We want our delorean to invoke one of these methods depending on what kind of engine it has. Since the methods are outside of the Car class and the delorean object, we can use #send to actually SEND a method to delorean if a certain condition is met:

if delorean.engine == "internal combustion"
delorean.send(:drive, "Doc")
elsif delorean.engine == "flux capacitor"
delorean.send(:time_travel, "Marty")

Since the delorean's @engine is equal to "flux capacitor", we sent it the method #time_travel. The first argument of #send is always going to be the method you want to send (in the form of a key), and the second argument is the argument taken by that method (in this case, will “Marty” will be the name accepted by :time_travel). With this in mind, we can expect the following output:

"SPARKS! FIRE! OMG, #{name} has traveled back in time to 1955! His mom has the hots for him! Ew!"

We have now successfully used #send to send the method #time_travel to the object delorean. Great Scott!