benjamins.to_enum

The Enumerable module in Ruby: Part I

In this article we’re going to explore the following topics:

  • The Enumerable module
  • The Enumerator class

Introduction

As Ruby is a fully object-oriented programming language, even traversing a collection can be accomplished directly through the collection itself.

This logic is encapsulated in the Enumerable module.

The Enumerable module

When the Enumerable module is included in a class then a bunch of:

  • Traversal methods
  • Searching methods
  • Sorting methods

are added to the including class.

This module is widely used among the most popular Ruby gems and projects— for example Ruby on Rails, devise, etc..

Also few important Ruby classes include this module, such as theArray, Hash, Range classes.

Let’s have an overview of the provided API for this module.

Let’s have a look to a traversal, a sorting and a searching method

Firstly, we use the well-known map method.

Then we use the sort method, which sorts by ascendent alphabet by default.

Finally, we use the searching method first to fetch the first item of the array.

Including the Enumerable module

A class that includes the Enumerable module must respond to the each method:

In the above example, a NoMethodError is raised due to the fact that, internally, the Enumerable#map method makes a call to the each method — which is not defined by the Users class.

So, let’s define it

Here we define a Users#each method that loops through the @users array — which is the data source of the Users class — then we yield each value of the @users array.

As the Users#each method is defined, then the Enumerable#map method can use it and get each user as argument of its block.

Feel free to read The yield keyword article if you are unfamiliar with the yield keyword.

Now, that we are more familiar with the Enumerable module, let’s dig into the Enumerator class.

The Enumerator class

An Enumerator is a data source, that is consumed by the Enumerable methods and can also be used for external iteration.

How to use an Enumerator?

If no argument is passed to map (and to almost all of the methods of the Enumerable module) then an instance of the Enumerator class is returned.

This enumerator is linked to the [1, 2, 3] array (data source) and the map method (data consumer)

In the above example, the map data consumer is executed in the context of the each method.

As you can you see, the enumerator executes only once the content of the block — as there is only 3 calls to the puts method.

This use case is not very efficient as we would prefer to call the map method as following [1, 2, 3].map { |n| puts n; n + 2 }.

Ok.. but here is a better one

As the map_with_index is not yet part of the Enumerable module, then a cool way to copy the behaviour of this method is by using the Enumerator returned by the map call without argument and then calling the Enumerator#with_index method.

So, the map data consumer is executed in the context of the with_index method.

Chaining Enumerators

The Ruby language gives you the possibility to chain Enumerators

Here, the map data consumer is executed in the context of the with_index data consumer which is linked to the context of the each method.

External iteration

An iteration is called internal when the iteration logic is encapsulated in the method.

For example, The Array#each method.

In contrary, an iteration is called external when the iteration logic is defined outside of a method.

Let’s see how the Enumerator module handles the external iteration

The Kernel#to_enum returns an instance of the Enumerator class with self (the [1,2,3] array in this case) as data source and the each method as default data consumer.

Moving the internal cursor

The Enumerator class provides a bunch of methods to manipulate an internal cursor that keeps the state of the external iteration.

The Enumerator#peek method returns the value contained at the cursor position.

The Enumerable#next method moves the cursor to the next position.

The Enumerator#next raises a StopIteration error when it is called and the cursor is at the last position of the data source.

The Enumerable#rewind method moves the cursor to the first position.

Voilà !

Feel free to have a look to the Part II.

May I have your attention please 🎤🎤

Feel free to subscribe here: www.rubycademy.com


Thank you for taking the time to read this post :-)

Feel free to 👏 and share this article if it has been useful for you. 🚀

Here is a link to my previous article: Numbers and Class Hierarchy in Ruby.