Making an Enumerable

A couple of weeks ago I wrote about some of the handy methods that are part of the Enumerable module in Ruby. This week I thought I would talk about how you can create a class that has all these handy methods by using the Enumerable mixin in your class.

Linked Lists

I wrote a simple linked list class to use as a demonstration. Linked lists are a chain of objects, usually called nodes. Each node points to the node after it. Here’s a picture where the nodes are represented as sets of two boxes. The first box in each pair holds the value. The second box points to the node after it. A box with a slash through it has value nil.

Linked list diagram for 7, 12, and 8

If it helps think about a linked list as being a set of clues in a treasure hunt. You start at one clue, it has some value and points you to the next clue, and so on until you get to the end. My node class looks like this:

1
2
3
4
5
6
7
8
class Node
  attr_accessor :value, :next

  def initialize value
    @value = value
    @next = nil
  end
end

Each node has a value. The initialize method sets the value when it creates a new node. Nodes also have a value called next which is the next node in the chain. New nodes have next set to nil since they are at the end of the chain.

The linked list class itself has two instance variables: head and tail. Head is the first value in the chain. Tail is the last value in the chain. There are also two methods push and pop. My implementation is a queue, so push adds the new value to the end, and pop removes a value at the beginning of the linked list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class LinkedList
  attr_accessor :head, :tail

  def initialize
    @head = nil
    @tail = nil
  end

  def push value
    n = Node.new value

    if @tail
      @tail.next = n
    else
      @head = n
    end

    @tail = n
  end

  def pop
    raise "Empty List" unless @head
    v = @head.value
    @head = @head.next
    v
  end
end

This is a basic implementation. Some edge cases are not covered, but this is sufficient to demo enumerable.

The Enumerable Module

Enumerable is a module in the Ruby Standard Library. That sentence contains a lot of technical jargon, but it means that Enumerable is a set of functionality that works on almost any collection. Hash, Array, and Range include Enumerable which is why methods like all? and each_cons work on all of them. Enumerable is also included in classes like Dir and IO allowing you to use methods like reject on collections of files or on file contents.

To include Enumerable in your class, you must implement each and put include Enumerable in the class definition. The each method must take a block and call that block for each successive element in the collection. For directories, that might mean each file the directory contains. For a range, each calls the block with each successive integer. Here’s the implementation of each for the linked list class.

1
2
3
4
5
6
7
8
def each
  node = @head

  while node do
    yield node
    node = node.next
  end
end

The each method first gets the head node, calls the block passing that node in using yield, and then sets node to the next node before looping. The last node always has nil as the next value, so the while condition ensures the loop stops once we have hit the end of the chain. That is all that you need to add all the methods of Enumerable in your class.

I can run each_with_index on my linked list. I can use each_cons and each_slice. I can call minmax on a linked list and get the minimum and maximum values stored in that list. I can even use one of my favorite methods, zip, to interleave values from a linked list with values in another linked list or another collection class altogether.

When To Mixin Enumerable

As you have seen, it is easy to make a class enumerable. However, just because you can doesn’t mean you should. You should only include Enumerable if your class is a collection, not when it has a collection. For example, a classroom class will have a collection of students. It also will probably have attributes for the teacher’s name, the location, meeting, and a subject or grade. While it would be handy to call classroom.each to iterate through the students, it is bad design to do so. You want to iterate through the students so classroom.students.each is more accurate and will lead to more maintainable code in the long term.

As a comparison, if you were implementing a RecordSet class to represent rows of a database it might make sense to include Enumerable. In this case, the class is a kind of collection. It probably doesn’t have any additional attributes beyond the data. Since it is a collection that you can enumerate, it makes sense to include Enumerable. If you are having problems telling the difference between these cases, look to see what additional attributes are in the class. If there are several attributes in addition to the collection or data, then it probably isn’t a good idea to include Enumerable. If the primary attribute is the collection then including Enumerable may make sense.