What Is a Ruby Enumerable and Why Would You Use It?

May 31, 2018 - 5 min read
What Is a Ruby Enumerable and Why Would You Use It?
Share

The Ruby Enumerable module deserves the top spot in the popularity contest. It packs so many great methods that it's hard to think of an application that wouldn't use it.

Since inheritance in Ruby is limited to a single parent class, modules allow you to share functionality between unrelated classes.

Enumerable is such a module. One that you can mix into your classes.

Let's see for example, what modules are included in the Array, and Hash classes.

Array.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]
Hash.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]

Why would you use Enumerable in your class?

The Enumerable module provides you with methods for searching, traversal, and sorting collections.

So if you're writing something that has to deal with collections, you can just mix in the Enumerable module, and you're good to go. There's no need for you to rewrite functionality that you can get for free.

How to make your class Enumerable

So, to use the Enumerable module, you simply include it in your class, and provide an each method that yields each value to a block. That's it!

class Person
  attr_accessor :name

  include Enumerable

  def initialize(name)
    @name = name
  end

  def each
    yield name
  end
end

Person.new("Cezar").to_a # => ["Cezar"]

You can learn more about attr_accessor if you want.

As you can see in the example above, I don't have to write the to_a method myself. I can get it for free by including the Enumerable module.

But let's see something more interesting.

class Person
  attr_accessor :emails
  include Enumerable
end

me = Person.new
me.emails = ["home@gmail.com", "work@gmail.com", "newsletters@gmail.com"]
me.emails.sort
# => ["home@gmail.com", "newsletters@gmail.com", "work@gmail.com"]

Again, I didn't have to write sort myself. That too comes from the Enumerable module.

Enumerable vs. Enumerator what is the difference?

Even though they look very similar, there is a big difference between the two.

The Enumerable module is the thing you include in your classes to get all the Enumerable methods.

The Enumerator is a class that includes the Enumerable module, just like other classes do. Its purpose is to create enumerable objects that can be chained together.

e = [1, 2, 3].map # => #<Enumerator: ...>
e.each_with_index { |n, i| n * i } # => [0, 2, 6]

Another cool thing you can do is call methods on it. For example if you want to get the next element, you can do this.

e = [1, 2, 3].map # => #<Enumerator: ...>
e.next # => 1
e.next # => 2

Build your own Enumerable module

What better way to learn what the Ruby Enumerable module does than to create one from scratch.

module Reduceable
  def reducer(acc)
    each do |value|
      acc = yield(acc, value)
    end
    acc
  end
end

class Person
  attr_accessor :emails
  include Reduceable

  def number_of_emails
    reducer(0) { |total, email| total + email.size }
  end

  def each
    yield emails
  end
end

me = Person.new
me.emails = ["home@gmail.com", "work@gmail.com", "newsletters@gmail.com"]
me.number_of_emails # => 3

The reducer method receives an initial value as the accumulator (0 in this case), and for each value, it passes both the accumulator and the email to the block it was given. Finally, it returns the accumulator.

You can do anything you want with those arguments inside the block. But this example counts the number of emails present in the emails array.

How to find the min/max value

That's a perfect example of an everyday use case that Enumerable provides.

[1, 2, 3].min # => 1
[1, 2, 3].max # => 3

You can also get more than one value back, by passing a count to min or max.

[1, 2, 3].min(2) # => [1, 2]
[1, 2, 3].max(2) # => [3, 2]

How to reset an enumerator

A neat trick you can do with an Enumerator is to rewind it to its first element.

e = [1, 2, 3].map # => #<Enumerator: ...>
e.next # => 1
e.next # => 2
e.rewind # => #<Enumerator: ...>
e.next # => 1

Reversing an enumerable

There's a reverse_each method that returns an Enumerator with the elements reversed.

[1, 2, 3].reverse_each.to_a # => [3, 2, 1]

How to remove duplicates enumerable lists

The method you want is called uniq.

[ "a", "a", "b", "b", "c" ].uniq # => ["a", "b", "c"]

Count vs. size vs. length

While size is nothing more than an alias for length, the difference between length and count is that count can take a block. If you pass a block to count, it only counts the number of elements for which the block returns truthy.

[1, :a, "b", nil, false].length # => 5
[1, :a, "b", nil, false].count { |n| n } # => 3

In the example above, 1, :a, and "a" are all truthy (the are not nil or `false). So the result is 3.

Is $< enumerable?

$< is just an alias for ARGF, and it includes the Enumerable module.

ARGF.class # => ARGF.class
ARGF.class.included_modules # => [Enumerable, PP::ObjectMixin, Kernel]
12 Project Ideas
Cezar Halmagean
Software development consultant with over a decade of experience in helping growing companies scale large Ruby on Rails applications. Has written about the process of building Ruby on Rails applications in RubyWeekly, SemaphoreCI, and Foundr.