1. Computing

Using the Enumerable Module

By

Using the Enumerable Module

You can iterate over many different types of objects in Ruby using the same set of methods. For example, you can use the includes? method to iterate over Arrays and Hashes to see if they include a specific object, and similarly use the map method to transform them. You might think each method implements these methods themselves, but you'd be wrong.

In Ruby, these types of methods are implemented in the Enumerable module. If your class implements the each method and includes the Enumerable module, your class will respond to a whole host of messages involving collections, including iterating, mapping, searching, etc.

The 'each' Method

The each method is at the core of all of these methods. In order to enumerate over your object, the methods in the Enumerable module must know how to visit each element of the collection. What this collection is and how it's stored is completely up to you. All the each method really has to do is visit each of the elements and yield the value to the block passed. What is really happening behind the scenes and how this data is stored is not a concern to anything but the each method.

To write your each method, the easiest way is to use the yield syntax. Visit each of your elements manually and yield the value to the calling code. In this example, the "collection" in question here is 3 of the 5 instance variables. It simply yields each of the three variables in question in turn.


#!/usr/bin/env ruby

class Family
  attr_accessor :name, :address
  attr_accessor :father, :mother, :child

  # We intend to iterate over the members
  # of the family, the family name and
  # address are irrelevant here
  def each
    yield "#{@father} #{@name}"
    yield "#{@mother} #{@name}"
    yield "#{@child} #{@name}"
  end
end

f = Family.new
f.name = 'Smith'
f.address = '13 Main St.'
f.father = 'Bob'
f.mother = 'Alice'
f.child = 'Carol'

f.each do|person|
  puts person
end

As you can see, none of the elements in this collection were stored in any kind of collection to begin with. They weren't in an array, or in a hash, they were just a few instance variables. Also, when iterated over using the each method, the family name was added to each. You can now iterate over them, but what about all that other stuff? The next step is to include Enumerable.


#!/usr/bin/env ruby

class Family
  attr_accessor :name, :address
  attr_accessor :father, :mother, :child

  include Enumerable

  # We intend to iterate over the members
  # of the family, the family name and
  # address are irrelevant here
  def each
    yield "#{@father} #{@name}"
    yield "#{@mother} #{@name}"
    yield "#{@child} #{@name}"
  end
end

f = Family.new
f.name = 'Smith'
f.address = '13 Main St.'
f.father = 'Bob'
f.mother = 'Alice'
f.child = 'Carol'

f.include?('Bob Smith')   # True
puts f.map{|p| "Family member: #{p}" }

Once you include Enumerable you have access to a whole host of methods that use the each method. Chief among them are map, which will iterate over your collection, call a block for each of the elements and form a new array out of the return values of that block and include?, which will iterate over the collection and return true if the passed value is present. There are many, many more though, for a full list you can refer to the Enumerable documentation.

Sorting

All of the methods from the Enumerable module are available to your class if you implement the each method. That is, all of the methods except the sort, min and max methods. These methods require the elements of the collection to support the spaceship operator <=>. Since most classes that come with Ruby (strings and numbers, for example) already implement this, your class probably already has access to these methods. For example in the previous example you could have "sorted" a family, and it would have sorted the family members alphabetically. They were yielded in a specific order, but they would have come out of the sort method in alphabetical order.


$ ruby enumerable_sort.rb 
# Calling puts f.sort
Alice Smith
Bob Smith
Carol Smith

If you're using objects that don't already implement the spaceship operator, you can implement it easily. But first you need to think about what it means to "sort" those objects. Which ones should come first in a sorted collection? In our example example, we'll sort the objects by age from youngest to oldest. Then when we sort out family, the children will come first and then the parents, even though the each method yields them in a different order.

But it's worth noting that this is an optional feature. If your collection never gets sorted, or min or max is never called then the objects in the collection don't need to support the spaceship operator. The only real requirement for the Enumerable module is to implement the each method.

This spaceship operator itself is nothing mysterious. It should return -1 if the object on the left of the operator (the callee object) is greater, 0 if they objects are equal and 1 if the object on the right (the object passed to the callee) is greater. Typically, the spaceship operator for some base type is used to implement the spaceship operator on your classes though.


#!/usr/bin/env ruby

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name, @age = name, age
  end

  def <=>(other)
    @age <=> other.age
  end

  def to_s
    "#{@name}, age #{@age}"
  end
end

class Family
  attr_accessor :name, :address
  attr_accessor :father, :mother, :child

  include Enumerable

  # We intend to iterate over the members
  # of the family, the family name and
  # address are irrelevant here
  def each
    yield @father
    yield @mother
    yield @child
  end
end

f = Family.new
f.name = 'Smith'
f.address = '13 Main St.'
f.father = Person.new('Bob', 43)
f.mother = Person.new('Alice', 41)
f.child = Person.new('Carol', 7)

puts f.sort

A few things to note here. The

Person class implements the spaceship operator, not the collection class itself. Also, the collection class no longer yields strings. If you were to yield strings as in the previous example, the strings would be sorted alphabetically, instead of by age. To make printing the people a bit easier instead of the hard-coded strings as yielded before, the Person#to_s method handles that now.

  1. About.com
  2. Computing
  3. Ruby
  4. Advanced Ruby
  5. Using the Enumerable Module

©2014 About.com. All rights reserved.