add support for decorate_collection for Draper 1.0

This commit is contained in:
John Bintz 2013-01-17 07:31:14 -05:00
parent 17be15a2e4
commit 83a36a2282
2 changed files with 94 additions and 41 deletions

View File

@ -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

View File

@ -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