Skip to content

Commit

Permalink
Merge pull request #5 from johnbintz/master
Browse files Browse the repository at this point in the history
Support for decorating collection for Draper >= 1.0
  • Loading branch information
johnbintz committed Mar 13, 2013
2 parents 17be15a + 83a36a2 commit 03e3962
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 35 deletions.
46 changes: 41 additions & 5 deletions lib/decorates_before_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand All @@ -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
Expand Down
77 changes: 47 additions & 30 deletions spec/decorates_before_rendering_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.