Sometimes Ruby, being like a functional language, unobtrusively forces us to use code blocks within iterators (such as map, each, etc.) More than offen, these codeblock are kinda one-liners and woo-do-|o|-end magic makes a code looking overdriven:

arr.each do |x|
  x.name
end.join(" ")

Well, there is curly-brackets-notation available, hence we may rewrite the code above within one line. But there is still a lot of absolutely unnecessary garbage hiding the core of what’s being actually done.

The good news is: ruby provides us with a syntactic sugar for that stuff. Let’s look at this:

arr.each (&:name).join(" ")

It is fully equivalent to the do-|o|-end codepiece above, but the readability is drastically improved.

How the hell does it work?

The &obj is evaluated in ruby in the following way:

  • if object is a block, it converts the block into a simple proc.
  • if object is a Proc, it converts the object into a block while preserving the lambda? status of the object.
  • if object is not a Proc, it first calls #to_proc on the object and then converts it into a block.

In our case the method is #to_proc on a Symbol’s instance (because :name.class == Symbol). The Symbol#to_proc method was originally added by ActiveSupport but has been integrated into Ruby since 1.8.7.

To enable this shorthand for classes other than Symbol, e. g. for an Array:

class Array
  def to_proc
    lambda { |recv| recv.send *self }
  end
end

Now we can write:

[ "Apple", "Orange", "Pear" ].map &[ :+, " is a fruit." ]
[ "Apple", "Orange", "Pear" ].map &[ :match, "[a-z]e" ]

yielding:

 #⇒ ["Apple is a fruit.", "Orange is a fruit.", "Pear is a fruit."]
 #⇒ [#<MatchData "le">, #<MatchData "ge">, nil]

Methods are being called on array elements (on Strings in the example above.)


Kinda same trick may be done for external methods using &method shorthand. Let’s say we have:

arr.each do |x|
  get_fullname x
end.join(" ")

Thus, assuming we have the get_fullname method defined, we can rewrite it as:

arr.each &method(:get_fullname)

In other words,

%w{ first second third }.map &method(:puts)

will print the array content out (expanding to { |s| puts s }).