When you treat your collections as enumerators, you get to use all your favorite functions like #map and #reduce without having to write any extra code. It's pretty awesome.

In the olden days, defining enumerators was a little cumbersome. You had to make a new class, include the Enumerable module, and define an #each function.

Ever since Ruby 1.9, though, we have a much more lightweight way to define enumerators on the fly. Let's take a look.

Introducing the Enumerator class

The Enumerator class lets you define a one-off enumerator, by using a block syntax. In the example below, we create an enumerator that returns an infinite series of random numbers.

e = Enumerator.new do |y|
  loop do
    y << rand(10) # The << operator "yields" a value.
  end
end

# Make the enumerator "yield" 10 values, then stop
puts e.first(10).inspect # => [6, 6, 7, 2, 2, 9, 6, 8, 2, 1]

You may have noticed that we're using the shift operator << in a strange way. This is a shortcut for the y.yield method. You will call it for each item in the enumerator. If this all seems a bit magical to you, don't worry. It is.

Collection size

Figuring out the size of a collection poses a problem for Ruby's lazy enumerators. To count the items in a collection, you have to load the entire collection - which is against the entire point of using lazy enumerators.

There is a work-around, kind of. If you happen to know the size of the collection at the time you create the Enumerator, you can provide it.

# You can pass the length as an argument to the constructor, if you have it
e = Enumerator.new(10) do |y|
  10.times { y << rand }
end

My real-world example

Just yesterday I was working on Honeybadger's new documentation site. It's built using Jekyll, and I was writing a plugin to create a table of contents based on the <h2> and <h3> tags in the documentation.

It's kind of awkward to figure out which <h3> tags belong to a section defined by <h2> tags. You have to parse the HTML using nokogiri, and then scan the resulting document. So I abstracted that bit of code out and made it an Enumerator. Here's what it looks like.

def subheadings(el)
  Enumerator.new do |y|
    next_el = el.next_sibling
    while next_el && next_el.name != "h2"
      if next_el.name == "h3"
        y << next_el
      end
      next_el = next_el.next_sibling
    end
  end
end

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required