Thu 3 Aug 2006
You may have noticed code that looks like this:
- array.each(&:notify!)
What, you may ask yourself, does this do? Firstly, each, map/collect, select, reject, inject, etc. are all methods on Enumerable that are included by various classes, like Array. They are the basis for dealing with Arrays in Ruby, and each is the one on which all the others are built (whether literally true, I don’t know, but they could be).
All these methods expect to pass each element of the Array in turn to a block (anonymous function), which normally looks something like this:
- array.each {|item| puts item.inspect}
However, there are times when you want to use the same block many times, in many different places. In such cases, you don’t really want your block to be repeated over and over again, so you encapsulate it in a Proc instance:
- my_block = Proc.new {|item| puts item.inspect}
- array1.each(&my_block)
- array2.each(&my_block)
Notice that you use the method-passing syntax of parenthesis rather than block syntax when calling each, and that both calls prefix the block with an ampersand. This is the syntax Ruby uses to indicate that this argument (which must be the last) should be treated as the block for this method call.
In the above examples, my_block is a Proc instance, which is exactly what Ruby expects. So far, so good. However, what if you don’t pass it a Proc?
- array.each(&"some string")
Aside from human confusion over exactly what is intended here, what will Ruby do? It will see that the object you’re saying to use as the block is not a Proc instance, and will try to convert it to one by calling the object’s to_proc method, if it exists.
I think that in the core language only Proc instances have this method (they return self), but Rails adds something:
- # in active_support/core_ext/symbol.rb
- class Symbol
- # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
- #
- # # The same as people.collect { |p| p.name }
- # people.collect(&:name)
- #
- # # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
- # people.select(&:manager?).collect(&:salary)
- def to_proc
- Proc.new { |obj, *args| obj.send(self, *args) }
- end
- end
This to_proc method returns a Proc instance whose contained block takes a target object and any number of trailing arguments. So here’s what we get:
- # these two are equivalent
- :name.to_proc
- Proc.new { |obj, *args| obj.name(*args) }
So we could do this:
- block = :name.to_proc
- array.map(&block)
However, as I said earlier, Ruby will call to_proc for us, so we can shorten it to this:
- array.map(&:name)
Remember that each, map, select, reject, etc. all only pass one argument: the item at the current point in the iteration. This means that in the block above args will be an empty array, so the block can usually be thought of as this:
- Proc.new { |obj| obj.send(self) }
And remember that self here is the symbol itself.