Permalink
Browse files

Merge pull request #5 from johnbintz/master

Support for decorating collection for Draper >= 1.0
  • Loading branch information...
johnbintz committed Mar 13, 2013
2 parents 17be15a + 83a36a2 commit 03e396298d793afc979cd6f956c32d50bf3aa86d
Showing with 88 additions and 35 deletions.
  1. +41 −5 lib/decorates_before_rendering.rb
  2. +47 −30 spec/decorates_before_rendering_spec.rb
@@ -25,11 +25,22 @@
#
# @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 @@ def self.decorates(*args)
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 @@ def render(*args)
private
def __decorate_ivars__
- return if __decorates__.nil? || __decorates__.empty?
+ return if (__decorates__.nil? || __decorates__.empty?) and
+ (__decorates_collection__.nil? || __decorates_collection__.empty?)
+
+ 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
- __decorates__.each do |ivar_names, options|
+ 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
@@ -4,44 +4,44 @@ class MyCompletelyFakeModelDecorator; end
class MyOtherCompletelyFakeModelDecorator; end
describe DecoratesBeforeRendering do
- # 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
+ 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
- let(:klass) do
- Class.new(superclass) do
- include DecoratesBeforeRendering
+ end
+ let(:klass) do
+ Class.new(superclass) do
+ include DecoratesBeforeRendering
- attr_reader :ivar, :ivars
+ attr_reader :ivar, :ivars
- def initialize(sentinel, ivar, ivars = nil)
- super(sentinel)
+ def initialize(sentinel, ivar, ivars = nil)
+ super(sentinel)
- @ivar = ivar
- @ivars = ivars
- end
+ @ivar = ivar
+ @ivars = ivars
end
end
- let(:instance) { klass.new(sentinel, ivar, ivars) }
- let(:args) { double('*args') }
+ 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
context "no ivars" do
it 'should render' do
sentinel.should_receive(:render).with(args)
@@ -109,5 +109,22 @@ def initialize(sentinel, ivar, ivars = nil)
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

0 comments on commit 03e3962

Please sign in to comment.