From 83a36a2282afee98da238a2ee3eaa5c67af415d8 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Thu, 17 Jan 2013 07:31:14 -0500 Subject: [PATCH] add support for decorate_collection for Draper 1.0 --- lib/decorates_before_rendering.rb | 46 +++++++++++-- spec/decorates_before_rendering_spec.rb | 89 +++++++++++++++---------- 2 files changed, 94 insertions(+), 41 deletions(-) diff --git a/lib/decorates_before_rendering.rb b/lib/decorates_before_rendering.rb index f3b3701..4ff5230 100644 --- a/lib/decorates_before_rendering.rb +++ b/lib/decorates_before_rendering.rb @@ -25,11 +25,22 @@ require 'active_support/concern' # # @thing_1 will be a ThingListDecorator (or contain them), and @thing_2 will be a Thing2Decorator. # +# For Draper 1.0 and above, collection elements are no longer decorated with +# Decorator.decorate(collection), but with Decorator.decorate_collection(collection). +# Specify that you want to decorate a collection, and with what decorator, with this syntax: +# +# class StuffController < ApplicationController +# include DecoratesBeforeRendering +# +# decorates_collection :things_1, :with => ThingListDecorator +# end +# module DecoratesBeforeRendering extend ActiveSupport::Concern included do class_attribute :__decorates__, :instance_writer => false + class_attribute :__decorates_collection__, :instance_writer => false class_eval do def self.decorates(*args) @@ -38,6 +49,15 @@ module DecoratesBeforeRendering self.__decorates__ ||= [] self.__decorates__ << [ args.map { |i| "@#{i}" }, options ] end + + def self.decorates_collection(*args) + options = args.extract_options! + + raise ArgumentError, ":with is required for now" if !options[:with] + + self.__decorates_collection__ ||= [] + self.__decorates_collection__ << [ args.map { |i| "@#{i}" }, options ] + end end end @@ -49,15 +69,31 @@ module DecoratesBeforeRendering private def __decorate_ivars__ - return if __decorates__.nil? || __decorates__.empty? + return if (__decorates__.nil? || __decorates__.empty?) and + (__decorates_collection__.nil? || __decorates_collection__.empty?) - __decorates__.each do |ivar_names, options| + if !__decorates__.nil? + __decorate_ivar_names__(__decorates__) do |ivar_name, ivar, options| + decorator = options.key?(:with) ? options.fetch(:with) : __decorator_for__(ivar) + decorated = decorator.decorate(ivar) + instance_variable_set(ivar_name, decorated) + end + end + + if !__decorates_collection__.nil? + __decorate_ivar_names__(__decorates_collection__) do |ivar_name, ivar, options| + decorated = options.fetch(:with).decorate_collection(ivar) + instance_variable_set(ivar_name, decorated) + end + end + end + + def __decorate_ivar_names__(ivars) + ivars.each do |ivar_names, options| ivar_names.each do |ivar_name| ivar = instance_variable_get(ivar_name) if ivar - decorator = options.key?(:with) ? options.fetch(:with) : __decorator_for__(ivar) - decorated = decorator.decorate(ivar) - instance_variable_set(ivar_name, decorated) + yield ivar_name, ivar, options end end end diff --git a/spec/decorates_before_rendering_spec.rb b/spec/decorates_before_rendering_spec.rb index 13a11ad..3a4ff5f 100644 --- a/spec/decorates_before_rendering_spec.rb +++ b/spec/decorates_before_rendering_spec.rb @@ -4,44 +4,44 @@ class MyCompletelyFakeModelDecorator; end class MyOtherCompletelyFakeModelDecorator; end describe DecoratesBeforeRendering do + let(:sentinel) { double(:sentinel) } + let(:ivar) { double('@ivar') } + let(:ivars) { double('@ivars') } + + # NOTE: This superclass is here so we know that the correct render gets + # called. It can't be defined in the subclass, or else that one + # will be the one that's used, as modules sit above their includers + # in the class hierarchy. + let(:superclass) do + Class.new do + def initialize(sentinel) + @sentinel = sentinel + end + + def render(*args) + @sentinel.render(*args) + end + end + end + let(:klass) do + Class.new(superclass) do + include DecoratesBeforeRendering + + attr_reader :ivar, :ivars + + def initialize(sentinel, ivar, ivars = nil) + super(sentinel) + + @ivar = ivar + @ivars = ivars + end + end + end + let(:instance) { klass.new(sentinel, ivar, ivars) } + let(:args) { double('*args') } + # NOTE: these are married together, so they're tested together. describe '::decorates + #render' do - let(:sentinel) { double(:sentinel) } - let(:ivar) { double('@ivar') } - let(:ivars) { double('@ivars') } - - # NOTE: This superclass is here so we know that the correct render gets - # called. It can't be defined in the subclass, or else that one - # will be the one that's used, as modules sit above their includers - # in the class hierarchy. - let(:superclass) do - Class.new do - def initialize(sentinel) - @sentinel = sentinel - end - - def render(*args) - @sentinel.render(*args) - end - end - end - let(:klass) do - Class.new(superclass) do - include DecoratesBeforeRendering - - attr_reader :ivar, :ivars - - def initialize(sentinel, ivar, ivars = nil) - super(sentinel) - - @ivar = ivar - @ivars = ivars - end - end - end - let(:instance) { klass.new(sentinel, ivar, ivars) } - let(:args) { double('*args') } - context "no ivars" do it 'should render' do sentinel.should_receive(:render).with(args) @@ -109,5 +109,22 @@ describe DecoratesBeforeRendering do end end end + + # for draper >= 1.0 + describe "#decorates_collection + #render" do + it "requires decorator class (for now)" do + expect { + klass.decorates_collection(:ivars) + }.to raise_error(ArgumentError) + end + + it "should decorate collection and render" do + klass.decorates_collection(:ivars, :with => MyCompletelyFakeModelDecorator) + subclass_instance = Class.new(klass).new(sentinel, ivar, ivars) + sentinel.should_receive(:render).with(args) + MyCompletelyFakeModelDecorator.should_receive(:decorate_collection).with(ivars) + subclass_instance.render(args) + end + end end