Reputation: 165145
I've written an Enumerable class to seamlessly and lazily fetch all pages of an API request.
class Pager
include Enumerable
def initialize(&fetch_next_page)
@fetch_next_page = fetch_next_page
reset
end
def reset
@page_number = 0
end
private def next_page
@page_number += 1
return @fetch_next_page.call(@page_number)
end
def each(&block)
if block_given?
while resp = next_page
resp.each(&block)
end
reset
else
to_enum(:each)
end
end
end
Here's an example of how it might be used.
pager = Pager.new do |page_number|
response = fetch_page( page: page_number, **some_options )
response.page <= response.total_pages ? response.stuff : false
end
But I've come to realize all this is doing is executing a block that returns an Enumerable until its false, and it's flattening the Enumerables.
pager = Pager.new { |page_number|
page_number <= 3 ? 1.upto(page_number) : false
}
# [1, 1, 2, 1, 2, 3]
puts pager.to_a.inspect
Is there a simpler way to do this? I've come close with Enumerator, but can't get the flattening to work.
def paginate(&fetch_next)
return Enumerator.new do |yielder|
page_number = 1
while ret = fetch_next.call(page_number)
yielder.yield(*ret)
page_number += 1
end
end
end
pager = paginate { |page_number|
page_number <= 3 ? 1.upto(page_number) : false
}
# [1, [1, 2], [1, 2, 3]]
puts pager.to_a.inspect
Upvotes: 2
Views: 55
Reputation: 21130
The reason the output for the enumerator isn't correct has indeed to do with the splat operator.
If you pass multiple values to yield they are yielded all at once, whereas you'd like to yield them one by one. Since you've got the block:
{ |page_number| page_number <= 3 ? 1.upto(page_number) : false }
This will result in 3 yields. The first with arguments 1
, the second with arguments 1, 2
and the third with arguments 1, 2, 3
. If you'd like to yield them as individual yields you'll have to change the following:
yielder.yield(*ret)
# should be changed to
ret.each { |e| yielder.yield e }
# or
ret.each { |e| yielder << e }
# depending on your preference
pager.to_a
#=> [1, 1, 2, 1, 2, 3]
Upvotes: 3