Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I like Enumerators <em>a lot</em> -- not just for existing types, but writing my own collections as Enumerator classes. Since switching to 1.9, I've twice had to build API adapters to external Web services that pull down large JSON or XML result sets. Sometimes I'd be capped on how many records I could retrieve at once, meaning I'd need to do multiple requests. (Get the first 500, then get records 501 to 1000, etc.)</p> <p>The "old" way I'd have processed these would have been to grab the first batch, iterate over it all at once with <code>.each</code> or <code>.collect</code>, and create an equal-sized array of Ruby objects. If I couldn't get all the records in one request, I'd loop through API requests as well, adding to the array each time. This means all the time is front-loaded, perceived as a slow retrieval, and I'm chewing up a <strong><em>lot</em></strong> of memory: for the source data, for an equal number of Ruby objects, and sometimes for intermediate array operations. This is wasteful when I'm probably only operating on one object at a time.</p> <p>With Enumerators, I can grab the first batch, hold the source data as my "authoritative" collection, and process and yield each Ruby object <em>as I step into it</em>. When I pass the last element, if I know there's more data to be pulled from the source, I can make the next API call then. (I.e., lazy loading.) This means a much faster return on the retrieval method call, and much better memory usage. Each Ruby object is eligible for garbage collection as soon as I'm done with it and have moved to the next one.</p> <p>An abstract implementation of the idea looks like this:</p> <pre><code>class ThingyCollection &lt; Enumerator attr_reader :total # Returns a new collection of thingies. def initialize(options={}) # Make the request for the first batch response = ThingyAPIClient.get_thingies(options) @total = response.total # Number of ALL thingies, not just first batch records = response.data # Some array of JSON/XML/etc. from the API # Create a closure which serves as our enumerator code enum = Proc.new do |yielder| counter = 0 # Initialize our iterator while counter &lt; @total # If we're at the end of this batch, get more records if counter == records.length more = ThingyAPIClient.get_next_thingies(counter, options) records += more.data end # Return a Ruby object for the current record yielder.yield Thingy.new(records[counter]) counter += 1 end end # Pass that closure to the Enumerator class super(&amp;enum) end end </code></pre> <p>Once you have that, you can walk over them like:</p> <pre><code>thingies = ThingyCollection.new(foo: bar) # Whatever search options are relevant puts "Our first thingy is #{thingies.next}" puts "Our second thingy is #{thingies.next}" thingies.rewind thingies.each do |thingy| do_stuff(thingy) end </code></pre> <p>What do you lose? Mostly the ability to easily jump to a particular element by reference. (Which means you also lose "last," sorts, etc.) Just getting <code>.next</code> and a couple of <code>.each</code> variants is not as rich as array functionality, but for my most common use cases it's all i need.</p> <p>Yes, you can do this with Ruby 1.8.7 thanks to backporting. But 1.9 is much faster at it thanks to the internal use of fibers. And if it hadn't been for 1.9, there wouldn't have been a 1.8.7, so I've decided it still qualifies as my favorite 1.9 feature.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload