Enumerable Magic – Boolean Methods

Learning over 50 methods in Ruby's Enumerable module can be a little daunting. It doesn't help that the official docs (which are awesome, by the way) sort the methods alphabetically, not by how they might be used. So for this tutorial, we'll start by looking at just those methods with a boolean (true or false) outcome. All of the source code for the following examples can be found on github:

https://github.com:bellmyer/rubycuts/enumerables

First, you should know that all examples here and in other Enumerable tutorials on this site will use one common set of example classes. A PetInventory class, which manages a collection of Pet objects, a file that contains a list of "pokey things", and a LogData class which iterates through Heroku log files in a RAM-friendly way.

all?

The all? method takes an enumerable collection and tests whether the given condition is true for every item in the collection. It returns true only if every element meets the condition.

Pet Inventory

The PetInventory class keeps track of how many pets our imaginary pet store has in stock at any given time. We also track the number of legs of each animal, in case somebody comes in not knowing what pet they want, but knowing how many legs it should have. Here's our initial pet inventory:

pet legs # in stock
dog 4 100
cat 4 50
fish 0 1000
scorpion 8 1
beetle 6 10,000
monkey 2 2
rock 0 0

Now, some code:

# view full source at https://github.com/rubycuts/enumerables/blob/master/lib/pet_inventory.rb

inventory = PetInventory.new

# do all pets have legs?
inventory.all?{|pet| pet.legs > 0}    #=> false

# are all pets in stock?
inventory.all?(&:in_stock?)    #=> false

# all pets have a name? 
inventory.all?{|pet| !pet.name.nil?}    #=> true

# if list is empty, is everything true? 
[].all?{|pet| pet.legs > 1000}    #=> true

Let's walk through these examples. Do all pets have legs? No, fish and rocks don't pass the legs > 0 test. Are all pets in stock? No, we're out of pet rocks. Do all pets have a name? Yes, no pet names are nil.

This last example is something I found interesting while playing with this method. If your list is empty, what does the all?

method return? By default, it returns true because no items failed the comparison test. Technically, no items passed either, which is a bit confusing, but I guess the authors picked the default that would work as you expect most of the time.

any?

The all? method takes an enumerable collection and tests whether the given condition is true for every item in the collection. It returns true only if every element meets the condition. Pet Inventory The PetInventory class keeps track of how many pets our imaginary pet store has in stock at any given time. We also track the number of legs of each animal, in case somebody comes in not knowing what pet they want, but knowing how many legs it should have. Here's our initial pet inventory:
petlegs# in stock
dog4100
cat450
fish01000
scorpion81
beetle610,000
monkey22
rock00
Now, some code:
# view full source at https://github.com/rubycuts/enumerables/blob/master/lib/pet_inventory.rb

inventory = PetInventory.new

# do all pets have legs?
inventory.all?{|pet| pet.legs > 0}    #=> false

# are all pets in stock?
inventory.all?(&:in_stock?)    #=> false

# all pets have a name? 
inventory.all?{|pet| !pet.name.nil?}    #=> true

# if list is empty, is everything true? 
[].all?{|pet| pet.legs > 1000}    #=> true
Let's walk through these examples. Do all pets have legs? No, fish and rocks don't pass the legs > 0 test. Are all pets in stock? No, we're out of pet rocks. Do all pets have a name? Yes, no pet names are nil. This last example is something I found interesting while playing with this method. If your list is empty, what does the all? method return? By default, it returns true because no items failed the comparison test. Technically, no items passed either, which is a bit confusing, but I guess the authors picked the default that would work as you expect most of the time.

Enumerable Magic – Introduction

Whether you realize it or not, you get a lot of use out of Ruby's Enumerable module. It shows up in arrays, hashes, file I/O, database interactions, and more. I'm going to explain what this module is, and how you can put it to use in your own Ruby classes.

What is a Module, and what is the Enumerable Module Specifically?

In Ruby, a module is a collection of methods, constants, and variables that can be included in classes to extend their functionality. The Enumerable module is responsible for a lot of the power behind arrays and hashes. If you've ever used methods like map, select, or count on an array, you weren't using methods built into the Array class itself - they were mixed in from Enumerable!

Here is the simplest version of a class which includes the Enumerable module:

class List
  include Enumerable

  def initialize
    @items = []
  end

  def each
    @items.each{|item| yield item}
  end
end

We start by including the Enumerable method. Then we provide an each method which all the Enumerable methods will use to iterate over the items in our list. While this example uses an each within an each, you don't have to. We can accomplish the same thing with successive yield calls in our each method. You can see this in an abbreviated version of the PetInventory class included with this lesson's source code:

# full source: https://github.com/rubycuts/enumerables/blob/master/lib/pet_inventory.rb

class PetInventory
  include Enumerable
  
  def initialize
    @dogs = Pet.new('dog', 4, 100)
    @cats = Pet.new('cat', 4, 50)
    @fish = Pet.new('fish', 0, 1000)
  end
  
  def each
    yield @dogs
    yield @cats
    yield @fish
  end
end

Calling yield three times in our each method means that any Enumerable methods will treat our PetInventory object as a collection with three items - a dog, a cat, and a fish.

This example also illustrates another good point. We were sort of cheating in our first example. Since @items is an array, it already has the enumerable methods available - no need to wrap it in the List class. In real life, almost every time you have a collection class, it's going to be a container for a groups of another class you've created. In this case, PetInventory is managing a collection of Pet objects.

Using Enumerable Methods

One of the easiest Enumerable methods to understand is sort_by. It will sort an enumerable collection based on the formula you give it. We'll use this method to illustrate the two main ways you will call most Enumerable methods. First, the long way:

pets = ['rooster', 'dog', 'fish']

# sort by pet name length
pets.sort_by{|pet| pet.length}  #=> ['dog', 'fish', 'rooster']

# sort by third letter in name
pets.sort_by{|pet| pet[2]}      #=> ['dog', 'rooster', 'fish']

We pass sort_by a block that will be run on each item in the list, and the list will be sorted based on the outcome. When the block will call a simple method on the object, as in the case of pet.length, there is a shorthand way to write this:

pets.sort_by(&:length)

This only works with simple methods that require no arguments, but you'd be surprised how often this is the case. Notice we couldn't sort by third letter using this shortcut, because that's a more complicated formula.

What Else?

Learning the Enumerable module will give you merlin-level powers over simple Hashes and Arrays, but learning to use Enumerable in your own classes will make you...whatever level wizard is three slots above merlin.

At first you might be hard pressed to think of places to use it, but it shows up more than you think. Ruby's File class allows you to iterate over each line in a file as if it was an Array, thanks to Enumerable. Many database wrappers do the same for select queries. In my example code, I flesh out a basic LogData class that iterates through Heroku logs so you can work with them without needing to load the entire log file into memory at once.

Like any great tool, once you start looking, you'll find more uses. I've just given you a hammer, and there's a world full of nails out there. Enjoy!