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 at a traversal, 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 search 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 an argument of its block.
Feel free to read
The yield keyword
article if you are unfamiliar with theyield
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 are 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 behavior of this method is by using the Enumerator
returned by the map
call without argument and then call 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 Enumerator
s
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.
On the 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 a data source and the each
method as a 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
method (as well as Enumerator#peek
) 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.
Feel free to have a look to the Part II.
Ruby Mastery
We’re currently finalizing our first online course: Ruby Mastery.
Join the list for an exclusive release alert! 🔔
Also, you can follow us on x.com as we’re very active on this platform. Indeed, we post elaborate code examples every day.
💚