Symbol#to_proc in Ruby

In the reading I’ve been doing around Ruby I tend to come across quite a few snippets which I don’t fully understand. The following is a line from ‘The Rails 3 Way’:

area_codes.map(&:to_s)

I’m familiar with the map method, but the ampersand-symbol syntax is new to me. Let’s investigate using an example.

class Team
  attr_accessor :location, :name
  
  def initialize(location,name)
    @location,@name = location,name
  end
end

teams = []
teams << Team.new('Denver','Broncos')
teams << Team.new('New England','Patriots')
teams << Team.new('New Orleans','Saints')
teams << Team.new('Green Bay','Packers')
teams << Team.new('Baltimore','Ravens')

So we have a list of NFL teams, but what if we now want a list of only their names? Coming from the C# world, I might write something like the following:

names = []
teams.each { |team| names << team.name }
p names

Terminal output

So this works, but – as you can imagine – Ruby has a better way.

p teams.map { |team| team.name }

This has exactly the same result. Now we’re a bit closer to the code we’re investigating in the first place. If I try the following, I get the same result again.

p teams.map(&:name)

This is actually just syntactic sugar for the previous snippet, but how does it work? The map method invokes a block argument for each element of the array and returns a new array containing the values returned by the block. But in the last snippet we don’t have a block, we have &:name – what’s up with that?

When we prepend an ampersand to an object in Ruby the interpreter attempts to convert it to a proc by calling to_proc on it. (Actually, the interpreter will first check to see if the object is a proc and act accordingly) So in this case the object :name is a symbol and the interpreter will call to_proc on it. The Symbol#to_proc method has been around in Ruby since version 1.8.7.

There is some complexity around how this is actually implemented, but basically a new Proc will be created which simply invokes the correct method on the object. As I said initially – it boils down to being syntactic sugar.

There is even more complexity around the implementation in Rails, since Rails allows you to pass in multiple arguments with the method call. This makes the functionality a bit more powerful but also makes the method a bit slower.

Pretty much what it boils down to is that the following two lines are equivalent.

collection.map { |item| item.something }
collection.map(&:something)

That’s some tasty sugar! Happy coding.