I often find that visual distractions slow me down. Reading simple code with familiar concepts feels cumbersome when I have to sort through repetition to find the meaning.
Take a glance at this code to see what I mean:
class Person
def street
address.street
end
def city
address.city
end
def state
address.state
end
end
Although the code is short, you have to think a bit before you realize that all of these methods do basically the same thing.
Ruby has a built-in way to bring the information out of this code: Forwardable.
I touched on using Forwardable in a previous article but we'll look closer at what's happening and why you want to use it.
The code above merely forwards the given method to another object. In this case if you try to get address details from a "Person", it will automatically pass it along to the related "address" object.
Given that the procedure is so simple and common in the code, I'd much rather have a way to read that information faster.
require 'forwardable'
class Person
extend Forwardable
delegate [:street, :city, :state] => :address
end
With this code, the concept of forwarding a message to a collaborating object is so clear that it reads like configuration.
Good configuration is fast and easy to understand.
Forwardable works essentially the same way that the longform code above does. You can provide a list of methods which should be sent to a particular object. Here's what a simplified version of what Forwardable looks like:
module Forwardable
def delegate(hash)
hash.each{ |methods, accessor|
methods.each{ |method|
instance_eval %{
def #{method}(*args, &block)
#{accessor}.__send__(:#{method}, *args, &block)
end
}
}
}
end
end
The "delegate" method accepts a hash where the keys are the method names to forward and the values are the object names to receive the forwarded messages.
For each item in the hash, it will loop through each method name and define a method using instance_eval and the generated string. Those methods send the message along to the object you specified.
The Forwardable code looks complicated, but it's merely 2 loops over a hash and an array, then the instance_eval. The ultimate result is the equivalent of the original code above. With some simple iteration and dynamically defining methods, Forwardable helps us strip away the unimportant parts and raises the valuable information to the top:
delegate [:street, :city, :state] => :address
This shows us what methods will be sent to which object without any additional code to sort through. What could be simpler or easier to scan and understand?