An Introduction to Ruby's Enumerable Module
If you're still writing for loops, stop; there's a better way. Exactly why merits some examples.
[1, 2, 3].each { |number| puts number }
That's a pretty straightforward one. It says: with each element in this array, execute the given block. But, what if we wanted to do something more complicated, like determine whether there were any even numbers in the array?
[1, 2, 3].any? { |number| number % 2 == 0 } # => true
[1, 3, 5].any? { |number| number % 2 == 0 } # => false
The any? method says: are there any elements in the array for which this block evaluates to true? We might also want to know whether all? of the elements were even.
[2, 4, 6].all? { |number| number % 2 == 0 } # => true
[1, 2, 3].all? { |number| number % 2 == 0 } # => false
We can see that making good use of Enumerable methods produces highly readable code. Before we move on, there are a couple more methods that are worth demonstrating.
We've already seen how we can easily look inside of an array, using any?, and all?. Conveniently, Enumerable provides similar methods for extracting objects. What if we wanted, for example, to retrieve all of the even numbers.
[1, 2, 3, 4].select { |number| number % 2 == 0 } # => [2, 4]
...or, the first even number...
[1, 2, 3, 4].detect { |number| number % 2 == 0 } # => 2
Let's pick up the pace.
Putting the Pieces Together
If I was writing some blogging software, I might want to display recent activity in the administration area. With a big array of notification objects, I can use select to easily divide them up for display, based on recency.
today = @notifications.select { |notification| notification.created_at > 1.day.ago.to_date }
yesterday = @notifications.select { |notification| notification.created_at < 1.day.ago.to_date && notification.created_at > 2.days.ago.to_date }
earlier = @notifications.select { |notification| notification.created_at < 2.days.ago.to_date }
I'm actually doing something very similar to this in an app I'm working on. Since I love fat models, I created some helper methods on AR::Base. They look (something) like this:
class ActiveRecord::Base
def today?
created_at > 1.day.ago.to_date
end
def yesterday?
created_at < 1.day.ago.to_date && created_at > 2.days.ago.to_date
end
def earlier?
created_at < 2.days.ago.to_date
end
end
So, now, we can greatly simplify our use of the select method, making it far clearer what we're up to.
today = @notifications.select(&:today?)
yesterday = @notifications.select(&:yesterday?)
earlier = @notifications.select(&:earlier?)
...wait, what? The ampersand operator, as described by the PickAxe book:
If the last argument to a method is preceded by an ampersand, Ruby assumes that it is a Proc object. It removes it from the parameter list, converts the Proc object into a block, and associates it with the method
But, a symbol isn't a Proc object. Luckily, ruby calls to_proc on the object to the right of the ampersand, in order to try and convert it to one. Symbol#to_proc is defined in ActiveSupport; it creates a block that calls the method named by the symbol on the object passed to it. So, for the above examples, the blocks send today?, yesterday?, or earlier?, respectively, to the each object that is yielded. Written out by hand, the expanded blocks would look like this:
today = @notifications.select { |notification| notification.today? }
yesterday = @notifications.select { |notification| notification.yesterday? }
earlier = @notifications.select { |notification| notification.earlier? }
For additional to_proc coolness, it's worth mentioning Reg Braithwaite's String#to_proc (or get the code). I won't go on any further about that today, though.
Let's look at one last example — this time from real software. In order to add support for polymorphic, deep nesting in resource_controller, I had to write an algorithm that would determine which of the possible sets of parent resources were present at the invocation of a controller action. This is done by testing to see which of the params (parent_type_id) are present. So, if I say belongs_to [:project, :message], [:project, :ticket] in my comments controller, I need to determine: for which of those two possible sets are both necessary params present? Since belongs_to is just a simple accessor, the algorithm is easy to write.
belongs_to.detect { |set| set.all? { |parent_type| params["#{parent_type}_id".to_sym].nil? } }
Recap
Enumerable's biggest advantage over the for loop, as we've seen, is clarity. Using Enumerable's looping power tools, like any?, all?, and friends makes your code read semantically. Especially combined with Symbol#to_proc, code "...seems to describe what I want done instead of how I want the computer to do it.", to quote Raganwald.
Finally, in order to take your Enumerable usage to the next level, there are a few more methods you should have in your toolbox.
I'd also recommend keeping the Enumerable, and Array documentation bookmarked, so that you can quickly refer to them. As a bonus, most of these methods exist in prototype.js, too; just take a look at the documentation.