Take me home

to_proc

Published September 08, 2008

You've probably seen this in Rails:

User.find(:all).map(&:name)

It is the equivalent of doing:

User.find(:all).map {|p| p.name }

The technique in question is called "symbol to proc". First, let's examine to_proc.

to_proc

def call_a_proc(&proc)
  proc.call
end

leethal = "some guy"

call_a_proc(&leethal)
# => TypeError: wrong argument type String (expected Proc)

Yeah, this is pretty odd. It's about time someone lets you in on a little secret:

class String
  def to_proc
    Proc.new { puts "I am a proc" }
  end
end

call_a_proc(&leethal)
# => I am a proc

The object responds to a to_proc method now, and Ruby calls it.

Back to Rails

So what is Rails doing that lets us do map(&:name)?

class Symbol
  def to_proc
    Proc.new { |*args| args.shift.__send__(self, *args) }
  end
end

That's right, it defines to_proc on Symbol.

Because of the to_proc magic, and because all methods that accepts blocks can take a proc instead, map(&:name) now works as a shortcut to map {|i| i.name }, thanks to this single line of code.

By the way, Rails could have been doing this:

class Symbol
  def to_proc
    Proc.new {|arg| arg.__send__(self) }
  end
end

Many people says Rails _should_ do this, because of performance issues. With Rails' implementation, though, this works:

[1, 2, 3].inject(&:+)
# => 6

Don't worry, you don't have to understand exactly what that to_proc code does. Feel free to investigate the matter until you do, though.


Questions or comments?

Feel free to contact me on Twitter, @augustl, or e-mail me at august@augustl.com.