Implemented decorated collection proxy

When decorating collections, returning an array or decorators
for each element is not enough. Often the original collection
is richer then an array, e.g. has pagination methods.
This commit adds DecoratedEnumerableProxy that is returned
when decorating a collection of models, and which proxies
missing methods to the original wrapped collection.
This commit is contained in:
Kliment Mamykin 2011-10-10 02:09:16 -04:00
parent f55962ddcb
commit d85ab06a15
2 changed files with 55 additions and 1 deletions

View File

@ -93,7 +93,7 @@ module Draper
# @param [Object] instance(s) to wrap
# @param [Object] context (optional)
def self.decorate(input, context = {})
input.respond_to?(:each) ? input.map{|i| new(i, context)} : new(input, context)
input.respond_to?(:each) ? DecoratedEnumerableProxy.new(input, self, context) : new(input, context)
end
# Access the helpers proxy to call built-in and user-defined
@ -158,5 +158,32 @@ module Draper
specified = self.allowed || (model.public_methods.map{|s| s.to_sym} - denied.map{|s| s.to_sym})
(specified - self.public_methods.map{|s| s.to_sym}) + FORCED_PROXY
end
class DecoratedEnumerableProxy
include Enumerable
def initialize(collection, klass, context)
@wrapped_collection, @klass, @context = collection, klass, context
end
# Implementation of Enumerable#each that proxyes to the wrapped collection
def each(&block)
@wrapped_collection.each { |member| block.call(@klass.new(member, @context)) }
end
# Implement to_arry so that render @decorated_collection is happy
def to_ary
@wrapped_collection.to_ary
end
def method_missing (meth, *args, &block)
@wrapped_collection.send(meth, *args, &block)
end
def to_s
"#<DecoratedEnumerableProxy of #{@klass} for #{@wrapped_collection.inspect}>"
end
end
end
end

View File

@ -150,6 +150,7 @@ describe Draper::Base do
its(:context) { should eq(context) }
end
end
end
context('.==') do
@ -159,6 +160,32 @@ describe Draper::Base do
end
end
describe "collection decoration" do
# Implementation of #decorate that returns an array
# of decorated objects is insufficient to deal with
# situations where the original collection has been
# expanded with the use of modules (as often the case
# with paginator gems) or is just more complex then
# an array.
module Paginator; def page_number; "magic_value"; end; end
Array.send(:include, Paginator)
let(:paged_array) { [Product.new, Product.new] }
subject { ProductDecorator.decorate(paged_array) }
it "should proxy all calls to decorated collection" do
paged_array.page_number.should == "magic_value"
subject.page_number.should == "magic_value"
end
it "should support Rails partial lookup for a collection" do
# to support Rails render @collection the returned collection
# (or its proxy) should implement #to_ary.
subject.respond_to?(:to_ary).should be true
subject.to_a.first.should == ProductDecorator.decorate(paged_array.first)
end
end
describe "a sample usage with denies" do
let(:subject_with_denies){ DecoratorWithDenies.new(source) }