The following is a guest post by Emily Xie and originally appeared on her blog. Emily is currently in the Ruby-003 class at The Flatiron School. You can follow her on Twitter here.
Metaprogramming is code that operates at the highest levels of abstraction so as to reduce total lines of hard coded processes and data. In addition to concision, it also allows flexibility for future edits.
One of the various metaprogramming concepts that we’ve covered in lecture at the Flatiron School includes dynamic dispatch. That is, calling methods on the fly, without knowing whether the given method exists for the object upon which it acts.
In this post, I’ll take a valiant (and hopefully unfailed) stab at the concept and its uses.
The Basics
Now, the way to dynamically dispatch in Ruby is by using the .send
method. Let’s demonstrate with a very basic example:
or
Here, .send
acts upon “Hello World”, with “upcase” passed in as an argument, either in string or symbol form. In my basic interpretation, .send
essentially relays a message (i.e. the argument) to the object, converts the argument into a method, and subsequently determines whether or not it exists for the given object. If so, Ruby evaluates it:
So, “upcase”
–> .upcase
. As it’s a valid method for strings, Ruby evaluates it accordingly. But let’s see what happens when we attempt to pass in this argument while evaluating an integer:
We see this result:
Ruby returns an undefined method error message, as it recognizes that no such method exists for integers.
Dispatching Methods with Arguments
While the above are examples of methods that take no arguments, .send
also accommodates ones that require them. Any remaining items within the parameters are passed into the method that .send
invokes.
The .send
in this instance, with self
referring to the instance of the main class, returns:
Why Dispatch Dynamically?
By now, I’ve laid out the basic groundwork for dynamic dispatch. As I’ve so far explained it disembodied from any context, you may wonder why we don’t directly call the method itself, rather than circuitously passing it through .send
.
Dynamic dispatch, as with all techniques of metaprogramming, can help write cleaner code. Let’s say we have a class HelloMachine
, which allows you to decide which language to greet in.
Notice how repetitive and cumbersome the if
statement contained within the speak
method is. We can easily pare this down using dynamic dispatch:
Here, we have created an APPROVED_COMMANDS
array, containing symbols of potential commands to enter into theHelloMachine
. The if
statement tests to see if the user’s input is contained within this array.
If so, the .send
kicks in, acting as a pivot that hands off the user’s command to one of the four existing methods: french, english, chinese, spanish.
Using .send
in conjunction with an array of potential methods, we’ve created code that is not only more succinct and encapsulated, but also more flexible. Any future methods would only require that you enter the corresponding symbol into theAPPROVED_COMMANDS
array, as opposed to hard coding even more “if” statements.
Keep Clean, and use with Caution
It’s also important to note in the example above how .send
, while helpful in code concision, can expose certain security holes. The APPROVED_COMMANDS
array is crucial in this scenario, as it limits the scope of what users can direct over.
Think, for example, what would happen if one were to .send
over “system”, “rm -rf /”
. You’d be …rF’ed!
Defining the Undefined
Clean code is nice, but the true beauty of dynamic dispatch comes in when we do not yet know the name of the method in advance—when it’s still represented in either string or symbol form. As this opens up another can of worms, I ask that you stay tuned: in a future post, I’ll follow up on dynamic dispatch and attempt to tackle how it might tie in with dynamic definition, in which we program to not only call, but create methods on the cuff.