Objective fun: Even methods can be objects
posted by andy, Thu Feb 14 13:00:00 UTC 2008
Even Methods Can Be Objects
One of the great things about Ruby is the extent to which everything is an object. There are significantly fewer 'primitives' in Ruby than in any other language with which I've worked. Even numbers are objects. Even methods are objects. Well, at least they can be.
Say Hello to My Little Friend Proc
Methods, at least code blocks, can become objects through the Proc class. As the second edition of Programming Ruby says it:
Proc objects are blocks of code that have been bound to a set of local variables. Once bound, the code may be called in different contexts and still access those variables.Huh? Basically, the Proc class lets you wrap up a block of code in an object, call it some place, and when it's called it's aware of the context from which it was called. GoF fans will probably recognize this as a form of the command pattern.
Okay, let's get down to brass tacks. As you'd expect, you create a new Proc instance by calling new. This method accepts a block with an arbitrary number of block parameters. Why an arbitrary number of them? Because those block parameters are going to become the parameters you use when you invoke the method you're wrapping up. Again, let's defer to Programming Ruby and look at their example:
def genTimes(factor)
return Proc.new {|n| n*factor }
end
times3 = genTimes(3)
times3.call(12) ยป 36
So what's going on here... the genTimes method accepts an argument called 'factor' and creates a Proc instance (a wrapped up method) with it. The wrapped up method accepts a single parameter it calls 'n' and returns the result of multiplying n and factor. Since this is a closure, the Proc is aware of the surrounding context and it pulls in 'factor' from genTimes. In the call 'genTimes(3)', factor is assigned the value 3 (duh), so the result of the call is to hand back an object-wrapped method that calculates n * 3.
As the Proc basically implements the command pattern you can use this object by invoking the command -- sending the 'call' message. In the example above the method expects one parameter (n) which is why the next-to-last line says, 'times3.call(12)'... the time3 object has a method that multiplies an argument by 3, and when called with the argument 12 you get 12 * 3 = 36. That's not quite so painful.
So Where's the Fun?
The 'fun' obviously lies in the way you can use the procs. In his brilliant book The Rails Way early adopter Obie Fernandez provides a great example that really struck a chord with me (I won't spoil the ending... buy the book!). At the end of his chapter on helpers Fernandez refactors a very specific partial into a very general custom helper. What struck a chord was that the partial essentially needed a 'description' method for the objects that it rendered. Obviously that was not a problem in the very specific original implementation, but it became one when trying to generalize the code. Enter Proc, stage right.
def cool_helper(collection, opts={})
...
opts[:description] ||= Proc.new{|item| itemd.description}
...
render :partial => 'shared/cool_partial',
:locals => {:description => opts[:description], ... }
end
In line 3 the refactored helper method creates a default Proc instance to handle the initial need for the description. All the proc does is call the 'description' method on the object that it's passed. The nice thing about this is that the default functionality matches what was being done in the original implementation. More importantly, though, this proc is being created as one of the optional parameters that can be supplied by the caller. That means if you would like to reuse the helper with an object that does not have a 'description' method (accessor or otherwise) then you can supply your own 'description' method to the renderer not in your object. For example, what if you now wanted to use this helper to display a list of sports teams and you wanted the description to be the 'name' attribute?
cool_helper my_teams, :description=>Proc.new{|item| item.name}
And there is where it gets fun. Since the proc is an object you can pass it between other objects, but what you're really passing is the ability to perform some custom functionality. The beauty, of course, is that the partial gets to remain blissfully ignorant of the implementation. It does not know or care whether it's invoking the description or name or any other attribute or method. It simply issues the 'call' method and the wrapped up function handles the rest. Something like this:
<div class="description"><br/>
<%= description.call(item) %><br/>
</div>
In the snippet above, just remember that 'description' is the name of the local variable passed into the partial through the :locals hash... and that variable is the object-wrapped method (Proc) that calls the description on your item.