Posted over 3 years ago. Visible to the public. Repeats.

Enumerators in Ruby

Starting with Ruby 1.9, most #each methods can be called without a block, and will return an enumerator. This is what allows you to do things like

['foo', 'bar', 'baz'].each.with_index.collect { |name, index| name * index } # -> ["", "bar", "bazbaz"]

If you write your own each method, it is useful to follow the same practice, i.e. write a method that

  • calls a given block for all entries
  • returns an enumerator, if no block is given

How to write a canonical each method

To write a method that adheres to the convention, simply

  • write your method for the case with a block given
  • return enum_for(:my_method) if no block is given

Ruby will take care of the rest.

class MyCollection def each return enum_for(:each) unless block_given? # the following depends on your use case while (item = fetch_next_item) yield item end end private def fetch_next_item # ... end end

Now you can either do

my_collection.each { |item| do_something_with(item) }


my_collection.each.take(100) # returns first 100 items, items 101+ will never be fetched.


I have used this to implement a service that fetched records via a REST api. The api used pagination. The code looked like this:

class VideoService def each_video(&block) return enum_for(:each_video) unless block_given? page = 0 loop do page += 1 records = fetch_page_of_records(page) if records.any? records.each(&block) else break end end end private def fetch_page_of_records(page_number) # do api call array_of_records end end do |video| process_video(video) end

This was useful because

  • it is lazy, i.e. page 2 is only requested after page 1 is done processing
  • in tests, I could write video_service.each_video.to_a.should == [video_1,...]

Lazy enumerators

It is possible to chain methods on an enumerator, for example you can write

video_service.each_video.with_index do |video, index| process_video(video, index) end

However, many of the chainable methods will break the laziness of the enumerator. For example

video_service.each_video.collect { |video| video }.each { |video| process_video(video) }

will fetch all videos, before processing them. To fix this, you can use the #lazy method:

video_services.each_video.lazy.collect { |video| video }.each { |video| process_video(video) }
Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Tobias Kraze
Last edit:
2 months ago
by Tobias Kraze
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Tobias Kraze to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more