Adhearsion Blog by Jay Phillips

Adhearsion, Ruby, VoIP, Entrepreneurship

The Methodphitamine

without comments

What amazes me most about Ruby is the emergence of hidden, powerful subtleties that lie under its surface just waiting to be discovered. A few months ago I discovered this useless, albeit cute snippet of Ruby’s fringe English-like qualities and, despite tens of thousands of people using Ruby, it wasn’t until just last year that the Symbol#to_proc hack was discovered that happily changed the way we refine and iterate our collections.

Well, ladies and gentlemen, I think I’ve developed a way that trumps Symbol#to_proc. Observe a few examples:

I call it The Methodphitamine. That’s Method ph it amine, a drug for Ruby addicts to code faster. Unlike Symbol#to_proc...

  • arguments can be given to the method
  • it’s not limited to just one method. Tail on as many as you like.
  • it’s more English-like with the intuitive use of it or its
  • even a method with its own block can be given to it

Let’s take the last of the examples above and convert this into its equivalent pure-Ruby and Symbol#to_proc way.

First, the pure-Ruby way:

User.find(:all).map{|x| x.contacts.map{|y| y.last_name.capitalize }}

And now with Symbol#to_proc:

User.find(:all).map{|x|x.contacts.map(&:last_name).map(&:capitalize)}

And now with the Methodphitamine once more:

User.find(:all).map &its.contacts.map(&its.last_name.capitalize)

Notice how even with Symbol#to_proc a block literal is still necessary because it simply can’t do nested arguments. The Methodphitamine fixes this by preserving all arguments and being more readable to boot.

So now, without further ado, here’s The Methodphitamine’s code, weighing in at just over 20 lines.

module Kernel
  protected
  def it() It.new end
  alias its it
end
 
class It
 
  undef_method(*(instance_methods - %w*__id__ __send__*))
 
  def initialize
    @methods = []
  end
 
  def method_missing(*args, &block)
    @methods << [args, block] unless args == [:respond_to?, :to_proc]
    self
  end
 
  def to_proc
    lambda do |obj|
      @methods.inject(obj) do |current,(args,block)|
        current.send(*args, &block)
      end
    end
  end
end

So how does The Methodphitamine work?

The it() and its() protected methods are added to Kernel so it can be called from anywhere in a Ruby script but not on any particular Object instance. They each simply return a new It instance.

The It class has all instance methods stripped from it (except the ones Ruby complains about) to ensure method_missing() catches everything. In the example [1,"2",3].map &its.class.name, the It object first receives the class() method with no arguments via method_missing(). This gets enqueued in the @methods Array and it returns itself to receive any more methods. It then receives the next method, namely name(), and enqueues that alongside the previous method. When no more methods exist, Ruby determines if the It instance has a to_proc() method by calling respond_to?(:to_proc) so this has to be ignored and self suffices as a Ruby true boolean.

Then the magic happens. Because map() takes a block (essentially a Proc argument), this can be substituted with a variable or method that returns a block as long as an ampersand prepends it to let the Ruby interpreter know to call to_proc() on it. The It#to_proc() method is invoked, building a custom, dynamic Proc. Because these enumerations yield a variable, the dynamic Proc is executed for each item in the collection and obj consequentially becomes a reference to the current item in the collection. We then run through the enqueued methods with inject(), passing along the return value of executing each method in the order received with arguments intact. When inject() is done, it simply returns the grand product which becomes the return value of the Proc itself. Simple, right? :)

I chose to define both it() and its() since methods in Ruby can semantically either mean “the result of this action” or the possessive “this attribute.” For example, it.to_s and it.sort_by are both conceptual actions and its.class.name and its.last are both conceptual attributes.

The idea of an it implied block argument comes from the Groovy guys. For example, this is valid Groovy:

[1,2,3,4].each { println it }

From the Groovy documentation on closures, “A closure always has at least one argument, which will be available within the body of the closure via the implicit parameter it if no explicit parameters are defined. The developer never has to declare the it variable – like the this parameter within objects, it is implicitly available.”

It’s about time for Ruby be on the receiving end of language beauty cross-pollination among the current generation of scripting languages.

I released this code in the public domain on RubyForge as the methodphitamine gem. To use, simply do

gem install methodphitamine
require 'methodphitamine'

When I wrote the Methodphitamine’s RSpec specifications, I noticed that RSpec’s own it() method gets undefined within the scope of each assertion’s block, allowing The Methodphitamine to behave normally even in specs. Very cool, RSpec!

If you discover any issues or have any improvement suggestions, feel free to post on The Methodphitamine Google Group.

It’s now standard in Adhearsion v0.8.0 among many other metaprogramming goodies. :)

Written by Jay Phillips

August 4th, 2007 at 8:30 pm

Posted in Programming, Ruby, Tips