Ruby Lazy Enumerators

1 minute read

Every time we use methods like map , collect , select , each we create an enumerator. Because of enumerators, methods can be chained like

[1, 2, 3].map { .... }.select { .... }

Calling each or select returns an enumerator as shown below:

[ 1 , 2 , 3 ]. each => #<Enumerator: ...> [ 1 , 2 , 3 ]. select => #<Enumerator: ...>

Normal Enumerator

Let’s say, we have to fetch 10 Twitter user profiles, who have @rails in their profile bio.

Assume, we have 1000 twitter user IDs. We may follow this approach to fetch the 10 users:

# Array of twitter user id's twitter_user_ids = [ ... ] twitter_user_ids . map { | user_id | TwitterClient . user ( user_id ) } . select { | data | data [ :description ]. includes? ( "@rails" ) } . first ( 10 )

We iterated over 1000 users, even if the first 10 users had @rails in their bio.

Lazy Enumerator

The above code can be improved by adding lazy as shown below:

# Array of twitter user id's twitter_user_ids = [ ... ] twitter_user_ids . lazy . map { | user_id | TwitterClient . user ( user_id ) } . select { | data | data [ :description ]. includes? ( "@rails" ) } . first ( 10 )

Here, we fetch users one-by-one without iterating over all 1000 users at once. The loop ends, when 10 users are fetched with the required condition.

Note:

Most Enumerable methods can be called with or without a block, but Enumerator::Lazy will always require a block.