[LRUG] Enumerator shenanigans

Andrew Stewart boss at airbladesoftware.com
Thu Nov 14 05:24:25 PST 2013


Hello El Rug,

I have written a method that fetches items in turn from a paged collection.  When called with a block it yields each item to the block.  When called without a block, it returns an Enumerator that spits out each item in turn as lazily as possible.

Let's say the collection has 1,000 items and we can fetch pages of 50 at a time.  This is what I want:

    # yields as it goes, retrieving pages as needed behind the scenes
    Item.find_each do |item|
      puts item.name
    end

    # only retrieves the first two pages
    # Ruby 1.9 doesn't do lazy map or reduce, I think?
    Item.find_each.take(75).map(&:price).reduce(:+)

    # only retrieves the first two pages
    # Ruby 2 allows this?
    Item.find_each.map(&:price).reduce(:+).take(75)

I'd like the code to work on Ruby 1.9 and 2.

So I have a working implementation but it looks, er, suboptimal.  What's the proper way to do this?

    class Item
      def self.find_each(params = {})
        if block_given?
          page = nil
          while page.nil? || page.current_page < page.total_pages
            if page.nil?
              page = all params
            else
              page = all params.merge(page: page.current_page + 1)
            end
            page.each do |item|
              yield item
            end
          end

        else
          Enumerator.new do |yielder|
            page = nil
            while page.nil? || page.current_page < page.total_pages
              if page.nil?
                page = all params
              else
                page = all params.merge(page: page.current_page + 1)
              end
              page.each do |item|
                yielder << item
              end
            end
          end
        end
      end

      def self.all(params = {})
        # snip
      end
    end

Thanks in advance,

Andy Stewart




More information about the Chat mailing list