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.