Compare commits

...

5 Commits

Author SHA1 Message Date
John Bintz 33b240196c bump version 2013-03-20 14:17:28 -04:00
John Bintz 83a36a2282 add support for decorate_collection for Draper 1.0 2013-01-17 07:31:14 -05:00
Rob Hanlon 17be15a2e4 Add CHANGELOG, bump version. 2012-10-17 18:26:16 -07:00
Rob Hanlon 1cb319dc05 Code cleanup. 2012-10-17 18:24:20 -07:00
Rob Hanlon 5685bbe0a1 Merge pull request #2 from johnbintz/decorates_with
Select the decorator to use for each @ivar
2012-10-17 18:14:09 -07:00
4 changed files with 121 additions and 57 deletions

10
CHANGELOG.markdown Normal file
View File

@ -0,0 +1,10 @@
# DecoratesBeforeRendering Changelog
## 0.0.2
* A specific decorator class can be provided using the :with option to decorates. Thanks to
@johnbintz!
## 0.0.1
* Initial release.

View File

@ -2,6 +2,7 @@
require "decorates_before_rendering/version"
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/class/attribute'
require 'active_support/concern'
# Decorates the specified fields. For instance, if you have
#
@ -24,16 +25,39 @@ require 'active_support/core_ext/class/attribute'
#
# @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
module ClassMethods
def decorates(*unsigiled_ivar_names)
options = {}
if unsigiled_ivar_names.last.instance_of?(::Hash)
options = unsigiled_ivar_names.pop
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)
options = args.extract_options!
self.__decorates__ ||= []
self.__decorates__ << [ args.map { |i| "@#{i}" }, options ]
end
self.__ivars_to_decorate__ ||= []
self.__ivars_to_decorate__ << [ unsigiled_ivar_names.map { |i| "@#{i}" }, options ]
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
@ -45,15 +69,31 @@ module DecoratesBeforeRendering
private
def __decorate_ivars__
ivars_to_decorate = self.class.__ivars_to_decorate__
return if (__decorates__.nil? || __decorates__.empty?) and
(__decorates_collection__.nil? || __decorates_collection__.empty?)
return if ivars_to_decorate.nil? or ivars_to_decorate.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
ivars_to_decorate.each do |ivar_names, options|
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|
if ivar = instance_variable_get(ivar_name)
decorator = (options[:with] || __decorator_for__(ivar)).decorate(ivar)
instance_variable_set(ivar_name, decorator)
ivar = instance_variable_get(ivar_name)
if ivar
yield ivar_name, ivar, options
end
end
end
@ -69,16 +109,13 @@ private
def __model_name_for__(ivar)
if ivar.respond_to?(:model_name)
ivar
source = ivar
elsif ivar.class.respond_to?(:model_name)
ivar.class
source = ivar.class
else
raise ArgumentError, "#{ivar} does not have an associated model"
end.model_name
end
end
def self.included(base)
base.class_attribute :__ivars_to_decorate__, :instance_accessor => false
base.extend ClassMethods
source.model_name
end
end

View File

@ -1,5 +1,5 @@
module DecoratesBeforeRendering
unless defined? DecoratesBeforeRendering::VERSION
VERSION = "0.0.1"
VERSION = "0.0.3"
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