Using Ruby Enumerators for Resumable Iterators
March 11, 2018
Here's a power technique: You can use Ruby Enumerators to create "resumable" iterators when you're extracting data from the application console.
In the past, When I had to some one-off analysis of some production data, I’d do something like this:
big_collection.each do |thingy| complex_expensive_computation_on(thingy) end
Invariably something would ‘asplode
OMG! Exception Raised: ... complex_expensive_computation_on() ...
Which I would dutifully fix, then I’d have to start. all. over. again.
I won’t go into the sublime complexities of Enumerables and Enumerators. Others have said it much better than I can. This unwrapping is exactly what happens when you pass a block to the enumerator returned by
each. If we unwrap it just a little bit using an enumerator instead of calling
enum = big_collection.each loop do thingy = enum.next complex_expensive_computation_on(thingy) end
Then (when) an exception is raised, I can “simply” handle it on the console, or edit the method, or say “lets just skip it, bad data,
Resuming from where we left off is a simple recapitulation of the loop:
loop do thingy = enum.next complex_expensive_computation_on(thingy) end
and we can carry on with the rest of the data!
When an enumerator gets to the end of its iterations, it raises the
StopIteration exception. That’s how
each with a block is implemented. Even moar better:
loop do...end has some extra Ruby magic sprinkles in it: It rescues
StopIteration for you so that when the enumerator does come to its timely end, there’s no exception raised.