Optimization: Optimize CollectionDecorator Enumerable behavior (#707)

* Optimization: Prevent duplicate iteration

* Don't delegate Enumerable instance methods since CollectionDecorator includes Enumerable itself
This commit is contained in:
Clemens Kofler 2017-04-12 03:32:09 +02:00 committed by Cliff Braton
parent a1d854a7f4
commit f6a7b17a8a
2 changed files with 51 additions and 1 deletions

View File

@ -15,7 +15,7 @@ module Draper
# to each item's decorator.
attr_accessor :context
array_methods = Array.instance_methods - Object.instance_methods
array_methods = Array.instance_methods - Object.instance_methods - Enumerable.instance_methods
delegate :==, :as_json, *array_methods, to: :decorated_collection
# @param [Enumerable] object
@ -42,6 +42,23 @@ module Draper
@decorated_collection ||= object.map{|item| decorate_item(item)}
end
# Optimization to prevent unnecessary iteration (useful for larger collections).
# Iterates over the collection, decorating objects as it goes. If the decorated collection is
# already set, iterate over it instead.
def each(&block)
if @decorated_collection
@decorated_collection.each(&block)
else
@decorated_collection = []
object.each do |item|
decorated_item = decorate_item(item)
@decorated_collection << decorated_item
block.call(decorated_item)
end
end
end
delegate :find, to: :decorated_collection
def to_s

View File

@ -288,5 +288,38 @@ module Draper
end
end
describe "#each" do
it "iterates over the collection, decorating as it goes" do
collection = [Product.new]
decorator = CollectionDecorator.new(collection)
expect(collection).to_not receive(:map)
decorator.each { |product| product.decorated? }
expect(decorator.instance_variable_get(:@decorated_collection)[0]).to be_decorated
end
it "uses decorated_collection if already set" do
decorated_collection = double(:decorated_collection)
decorator = CollectionDecorator.new([])
decorator.instance_variable_set(:@decorated_collection, decorated_collection)
expect(decorated_collection).to receive(:each)
decorator.each
end
end
describe "Enumerable methods" do
it "doesn't delegate Enumerable methods to its decorated collection" do
decorated_collection = double(:decorated_collection)
decorator = CollectionDecorator.new([])
decorator.instance_variable_set(:@decorated_collection, decorated_collection)
expect(decorated_collection).to_not receive(:map)
decorator.map
end
end
end
end