mirror of
https://github.com/drapergem/draper
synced 2023-03-27 23:21:17 -04:00
Restore context. Collections inherit context by default, but associations
can specify either a static context or a lambda. Validation for options in a number of methods.
This commit is contained in:
parent
059ecbcc89
commit
b2f63882ec
9 changed files with 471 additions and 69 deletions
|
@ -38,6 +38,13 @@ module Draper
|
|||
end
|
||||
end
|
||||
|
||||
def self.validate_options(options, *valid_keys)
|
||||
options_errors = options.keys - valid_keys
|
||||
unless options_errors.empty?
|
||||
raise ArgumentError, "Invalid option keys: #{options_errors.map {|k| k.inspect}.join(', ')}", caller
|
||||
end
|
||||
end
|
||||
|
||||
class UninferrableDecoratorError < NameError
|
||||
def initialize(klass)
|
||||
super("Could not infer a decorator for #{klass}.")
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
require 'draper'
|
||||
|
||||
module Draper
|
||||
class CollectionDecorator
|
||||
include Enumerable
|
||||
include ViewHelpers
|
||||
|
||||
attr_accessor :source, :options, :decorator_class
|
||||
protected :options, :options=
|
||||
alias_method :to_source, :source
|
||||
|
||||
delegate :as_json, *(Array.instance_methods - Object.instance_methods), to: :decorated_collection
|
||||
|
||||
# @param source collection to decorate
|
||||
# @param options [Hash] passed to each item's decorator (except
|
||||
# for the keys listed below)
|
||||
# @option options [Class,Symbol] :with the class used to decorate
|
||||
# @param [Hash] options (optional)
|
||||
# @option options [Class, Symbol] :with the class used to decorate
|
||||
# items, or `:infer` to call each item's `decorate` method instead
|
||||
# @option options [Hash] :context context available to each item's decorator
|
||||
def initialize(source, options = {})
|
||||
@source = source
|
||||
@decorator_class = options.delete(:with) || self.class.inferred_decorator_class
|
||||
Draper.validate_options(options, :with, :context)
|
||||
@options = options
|
||||
end
|
||||
|
||||
|
@ -56,18 +60,24 @@ module Draper
|
|||
"#<CollectionDecorator of #{decorator_class} for #{source.inspect}>"
|
||||
end
|
||||
|
||||
def options=(options)
|
||||
each {|item| item.options = options }
|
||||
@options = options
|
||||
# Accessor for `:context` option
|
||||
def context
|
||||
options.fetch(:context, {})
|
||||
end
|
||||
|
||||
# Setter for `:context` option
|
||||
def context=(input)
|
||||
options[:context] = input
|
||||
each {|item| item.context = input } unless respond_to?(:loaded?) && !loaded?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def decorate_item(item)
|
||||
if decorator_class == :infer
|
||||
item.decorate(options)
|
||||
item.decorate(context: context)
|
||||
else
|
||||
decorator_class.decorate(item, options)
|
||||
decorator_class.decorate(item, context: context)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
require 'draper'
|
||||
|
||||
module Draper
|
||||
class DecoratedAssociation
|
||||
|
||||
attr_reader :source, :association, :options
|
||||
attr_reader :base, :association, :options
|
||||
|
||||
def initialize(source, association, options)
|
||||
@source = source
|
||||
def initialize(base, association, options)
|
||||
@base = base
|
||||
@association = association
|
||||
Draper.validate_options(options, :with, :scope, :context)
|
||||
@options = options
|
||||
end
|
||||
|
||||
|
@ -14,6 +17,10 @@ module Draper
|
|||
decorate
|
||||
end
|
||||
|
||||
def source
|
||||
base.source
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def undecorated
|
||||
|
@ -25,7 +32,7 @@ module Draper
|
|||
end
|
||||
|
||||
def decorate
|
||||
@decorated ||= decorator_class.send(decorate_method, undecorated, options)
|
||||
@decorated ||= decorator_class.send(decorate_method, undecorated, decorator_options)
|
||||
end
|
||||
|
||||
def decorate_method
|
||||
|
@ -51,5 +58,15 @@ module Draper
|
|||
end
|
||||
end
|
||||
|
||||
def decorator_options
|
||||
decorator_class # Ensures options[:with] = :infer for unspecified collections
|
||||
|
||||
dec_options = collection? ? options.slice(:with, :context) : options.slice(:context)
|
||||
dec_options[:context] = base.context unless dec_options.key?(:context)
|
||||
if dec_options[:context].respond_to?(:call)
|
||||
dec_options[:context] = dec_options[:context].call(base.context)
|
||||
end
|
||||
dec_options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'active_support/core_ext/array/extract_options'
|
||||
require 'draper'
|
||||
|
||||
module Draper
|
||||
class Decorator
|
||||
|
@ -6,13 +7,15 @@ module Draper
|
|||
include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
|
||||
|
||||
attr_accessor :source, :options
|
||||
protected :options, :options=
|
||||
|
||||
alias_method :model, :source
|
||||
alias_method :to_source, :source
|
||||
|
||||
# Initialize a new decorator instance by passing in
|
||||
# an instance of the source class. Pass in an optional
|
||||
# context inside the options hash is stored for later use.
|
||||
# :context inside the options hash which is available
|
||||
# for later use.
|
||||
#
|
||||
# A decorator cannot be applied to other instances of the
|
||||
# same decorator and will instead result in a decorator
|
||||
|
@ -23,9 +26,11 @@ module Draper
|
|||
#
|
||||
# @param [Object] source object to decorate
|
||||
# @param [Hash] options (optional)
|
||||
# @option options [Hash] :context context available to the decorator
|
||||
def initialize(source, options = {})
|
||||
source.to_a if source.respond_to?(:to_a) # forces evaluation of a lazy query from AR
|
||||
@source = source
|
||||
Draper.validate_options(options, :context)
|
||||
@options = options
|
||||
handle_multiple_decoration if source.is_a?(Draper::Decorator)
|
||||
end
|
||||
|
@ -72,9 +77,15 @@ module Draper
|
|||
# @param [Symbol] association name of association to decorate, like `:products`
|
||||
# @option options [Class] :with the decorator to apply to the association
|
||||
# @option options [Symbol] :scope a scope to apply when fetching the association
|
||||
# @option options [Hash, #call] :context context available to decorated
|
||||
# objects in collection. Passing a `lambda` or similar will result in that
|
||||
# block being called when the association is evaluated. The block will be
|
||||
# passed the base decorator's `context` Hash and should return the desired
|
||||
# context Hash for the decorated items.
|
||||
def self.decorates_association(association, options = {})
|
||||
Draper.validate_options(options, :with, :scope, :context)
|
||||
define_method(association) do
|
||||
decorated_associations[association] ||= Draper::DecoratedAssociation.new(source, association, options)
|
||||
decorated_associations[association] ||= Draper::DecoratedAssociation.new(self, association, options)
|
||||
decorated_associations[association].call
|
||||
end
|
||||
end
|
||||
|
@ -82,7 +93,8 @@ module Draper
|
|||
# A convenience method for decorating multiple associations. Calls
|
||||
# decorates_association on each of the given symbols.
|
||||
#
|
||||
# @param [Symbols*] associations name of associations to decorate
|
||||
# @param [Symbols*] associations names of associations to decorate
|
||||
# @param [Hash] options passed to `decorate_association`
|
||||
def self.decorates_associations(*associations)
|
||||
options = associations.extract_options!
|
||||
associations.each do |association|
|
||||
|
@ -128,7 +140,9 @@ module Draper
|
|||
# for the keys listed below)
|
||||
# @option options [Class,Symbol] :with (self) the class used to decorate
|
||||
# items, or `:infer` to call each item's `decorate` method instead
|
||||
# @option options [Hash] :context context available to decorated items
|
||||
def self.decorate_collection(source, options = {})
|
||||
Draper.validate_options(options, :with, :context)
|
||||
Draper::CollectionDecorator.new(source, options.reverse_merge(with: self))
|
||||
end
|
||||
|
||||
|
@ -171,6 +185,16 @@ module Draper
|
|||
source.present?
|
||||
end
|
||||
|
||||
# Accessor for `:context` option
|
||||
def context
|
||||
options.fetch(:context, {})
|
||||
end
|
||||
|
||||
# Setter for `:context` option
|
||||
def context=(input)
|
||||
options[:context] = input
|
||||
end
|
||||
|
||||
# For ActiveModel compatibilty
|
||||
def to_model
|
||||
self
|
||||
|
|
|
@ -16,33 +16,97 @@ describe Draper::CollectionDecorator do
|
|||
subject.map{|item| item.source}.should == source
|
||||
end
|
||||
|
||||
context "with options" do
|
||||
subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, some: "options") }
|
||||
context "with context" do
|
||||
subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, context: {some: 'context'}) }
|
||||
|
||||
its(:options) { should == {some: "options"} }
|
||||
its(:context) { should == {some: 'context'} }
|
||||
|
||||
it "passes options to the individual decorators" do
|
||||
it "passes context to the individual decorators" do
|
||||
subject.each do |item|
|
||||
item.options.should == {some: "options"}
|
||||
item.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#options=" do
|
||||
it "updates the options on the collection decorator" do
|
||||
subject.options = {other: "options"}
|
||||
subject.options.should == {other: "options"}
|
||||
end
|
||||
|
||||
it "updates the options on the individual decorators" do
|
||||
subject.options = {other: "options"}
|
||||
it "does not tie the individual decorators' contexts together" do
|
||||
subject.each do |item|
|
||||
item.options.should == {other: "options"}
|
||||
item.context.should == {some: 'context'}
|
||||
item.context = {alt: 'context'}
|
||||
item.context.should == {alt: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#context=" do
|
||||
context "with loaded? unimplemented" do
|
||||
it "updates the context on the collection decorator" do
|
||||
subject.context = {other: 'context'}
|
||||
subject.context.should == {other: 'context'}
|
||||
end
|
||||
|
||||
it "updates the context on the individual decorators" do
|
||||
subject.context = {other: 'context'}
|
||||
subject.each do |item|
|
||||
item.context.should == {other: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
it "updates the context on the individual decorators following modification" do
|
||||
subject.each do |item|
|
||||
item.context = {alt: 'context'}
|
||||
end
|
||||
subject.context = {other: 'context'}
|
||||
subject.each do |item|
|
||||
item.context.should == {other: 'context'}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We have to stub out loaded? because the test environment uses an Array,
|
||||
# not an ActiveRecord::Associations::CollectionProxy
|
||||
context "with loaded? true" do
|
||||
before(:each) { subject.stub(:loaded?).and_return(true) }
|
||||
|
||||
it "updates the context on the individual decorators following modification" do
|
||||
subject.each do |item|
|
||||
item.context = {alt: 'context'}
|
||||
end
|
||||
subject.context = {other: 'context'}
|
||||
subject.each do |item|
|
||||
item.context.should == {other: 'context'}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with loaded? false" do
|
||||
before(:each) { subject.stub(:loaded?).and_return(false) }
|
||||
|
||||
it "does not trigger enumeration prematurely" do
|
||||
subject.should_not_receive(:each)
|
||||
subject.context = {other: 'context'}
|
||||
end
|
||||
|
||||
it "the individual decorators still get context upon enumeration" do
|
||||
subject.context = {other: 'context'}
|
||||
subject.each do |item|
|
||||
item.context.should == {other: 'context'}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
describe "options validation" do
|
||||
let(:valid_options) { {with: ProductDecorator, context: {}} }
|
||||
|
||||
it "does not raise error on valid options" do
|
||||
expect { Draper::CollectionDecorator.new(source, valid_options) }.to_not raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Invalid option keys: :foo')
|
||||
end
|
||||
end
|
||||
|
||||
context "when the :with option is given" do
|
||||
context "and the decorator can't be inferred from the class" do
|
||||
subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
|
||||
|
@ -88,6 +152,29 @@ describe Draper::CollectionDecorator do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#options" do
|
||||
subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, context: {some: 'context'}) }
|
||||
|
||||
it "stores options internally" do
|
||||
subject.send(:options).should == {context: {some: 'context'}}
|
||||
end
|
||||
|
||||
it "blocks options externally" do
|
||||
expect { subject.options }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#options=" do
|
||||
it "permits modification of options internally" do
|
||||
subject.send(:options=, {context: {some: 'other_context'}})
|
||||
subject.send(:options).should == {context: {some: 'other_context'}}
|
||||
end
|
||||
|
||||
it "blocks options= externally" do
|
||||
expect { subject.options = {context: {some: 'other_context'}} }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find" do
|
||||
context "with a block" do
|
||||
it "decorates Enumerable#find" do
|
||||
|
@ -123,14 +210,14 @@ describe Draper::CollectionDecorator do
|
|||
end
|
||||
|
||||
describe "#localize" do
|
||||
before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
|
||||
before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
|
||||
|
||||
it "delegates to helpers" do
|
||||
subject.localize(:an_object, some: "options")
|
||||
subject.localize(:an_object, some: 'parameter')
|
||||
end
|
||||
|
||||
it "is aliased to #l" do
|
||||
subject.l(:an_object, some: "options")
|
||||
subject.l(:an_object, some: 'parameter')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ describe Draper::Decoratable do
|
|||
subject.decorate.source.should be subject
|
||||
end
|
||||
|
||||
it "accepts options" do
|
||||
decorator = subject.decorate(some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "accepts context" do
|
||||
decorator = subject.decorate(context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
|
||||
it "is not memoized" do
|
||||
|
@ -153,9 +153,9 @@ describe Draper::Decoratable do
|
|||
decorator.source.should be Product.scoped
|
||||
end
|
||||
|
||||
it "accepts options" do
|
||||
decorator = Product.decorate(some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "accepts context" do
|
||||
decorator = Product.decorate(context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
|
||||
it "is not memoized" do
|
||||
|
|
|
@ -1,10 +1,52 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Draper::DecoratedAssociation do
|
||||
let(:decorated_association) { Draper::DecoratedAssociation.new(source, association, options) }
|
||||
let(:decorated_association) { Draper::DecoratedAssociation.new(base, association, options) }
|
||||
let(:source) { Product.new }
|
||||
let(:base) { source.decorate }
|
||||
let(:options) { {} }
|
||||
|
||||
describe "#initialize" do
|
||||
describe "options validation" do
|
||||
let(:association) { :similar_products }
|
||||
let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
|
||||
|
||||
it "does not raise error on valid options" do
|
||||
expect { Draper::DecoratedAssociation.new(base, association, valid_options) }.to_not raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
expect { Draper::DecoratedAssociation.new(base, association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Invalid option keys: :foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#base" do
|
||||
subject { decorated_association.base }
|
||||
let(:association) { :similar_products }
|
||||
|
||||
it "returns the base decorator" do
|
||||
should be base
|
||||
end
|
||||
|
||||
it "returns a Decorator" do
|
||||
subject.class.should == ProductDecorator
|
||||
end
|
||||
end
|
||||
|
||||
describe "#source" do
|
||||
subject { decorated_association.source }
|
||||
let(:association) { :similar_products }
|
||||
|
||||
it "returns the base decorator's source" do
|
||||
should be base.source
|
||||
end
|
||||
|
||||
it "returns a Model" do
|
||||
subject.class.should == Product
|
||||
end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
subject { decorated_association.call }
|
||||
|
||||
|
@ -128,5 +170,144 @@ describe Draper::DecoratedAssociation do
|
|||
subject.source.should be scoped
|
||||
end
|
||||
end
|
||||
|
||||
context "base has context" do
|
||||
let(:association) { :similar_products }
|
||||
let(:base) { source.decorate(context: {some: 'context'}) }
|
||||
|
||||
context "when no context is specified" do
|
||||
it "it should inherit context from base" do
|
||||
subject.context.should == {some: 'context'}
|
||||
end
|
||||
|
||||
it "it should share context hash with base" do
|
||||
subject.context.should be base.context
|
||||
end
|
||||
end
|
||||
|
||||
context "when static context is specified" do
|
||||
let(:options) { {context: {other: 'context'}} }
|
||||
|
||||
it "it should get context from static option" do
|
||||
subject.context.should == {other: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
context "when lambda context is specified" do
|
||||
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
||||
|
||||
it "it should get generated context" do
|
||||
subject.context.should == {some: 'context', other: 'protext'}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorator_options" do
|
||||
subject { decorated_association.send(:decorator_options) }
|
||||
|
||||
context "collection association" do
|
||||
let(:association) { :similar_products }
|
||||
|
||||
context "no options" do
|
||||
it "should return default options" do
|
||||
should == {with: :infer, context: {}}
|
||||
end
|
||||
|
||||
it "should set with: to :infer" do
|
||||
decorated_association.send(:options).should == options
|
||||
subject
|
||||
decorated_association.send(:options).should == {with: :infer}
|
||||
end
|
||||
end
|
||||
|
||||
context "option with: ProductDecorator" do
|
||||
let(:options) { {with: ProductDecorator} }
|
||||
it "should pass with: from options" do
|
||||
should == {with: ProductDecorator, context: {}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option scope: :to_a" do
|
||||
let(:options) { {scope: :to_a} }
|
||||
it "should strip scope: from options" do
|
||||
decorated_association.send(:options).should == options
|
||||
should == {with: :infer, context: {}}
|
||||
end
|
||||
end
|
||||
|
||||
context "base has context" do
|
||||
let(:base) { source.decorate(context: {some: 'context'}) }
|
||||
|
||||
context "no options" do
|
||||
it "should return context from base" do
|
||||
should == {with: :infer, context: {some: 'context'}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option context: {other: 'context'}" do
|
||||
let(:options) { {context: {other: 'context'}} }
|
||||
it "should return specified context" do
|
||||
should == {with: :infer, context: {other: 'context'}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option context: lambda" do
|
||||
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
||||
it "should return specified context" do
|
||||
should == {with: :infer, context: {some: 'context', other: 'protext'}}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "singular association" do
|
||||
let(:association) { :previous_version }
|
||||
|
||||
context "no options" do
|
||||
it "should return default options" do
|
||||
should == {context: {}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option with: ProductDecorator" do
|
||||
let(:options) { {with: ProductDecorator} }
|
||||
it "should strip with: from options" do
|
||||
should == {context: {}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option scope: :decorate" do
|
||||
let(:options) { {scope: :decorate} }
|
||||
it "should strip scope: from options" do
|
||||
decorated_association.send(:options).should == options
|
||||
should == {context: {}}
|
||||
end
|
||||
end
|
||||
|
||||
context "base has context" do
|
||||
let(:base) { source.decorate(context: {some: 'context'}) }
|
||||
|
||||
context "no options" do
|
||||
it "should return context from base" do
|
||||
should == {context: {some: 'context'}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option context: {other: 'context'}" do
|
||||
let(:options) { {context: {other: 'context'}} }
|
||||
it "should return specified context" do
|
||||
should == {context: {other: 'context'}}
|
||||
end
|
||||
end
|
||||
|
||||
context "option context: lambda" do
|
||||
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
||||
it "should return specified context" do
|
||||
should == {context: {some: 'context', other: 'protext'}}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,13 +7,30 @@ describe Draper::Decorator do
|
|||
let(:source) { Product.new }
|
||||
|
||||
describe "#initialize" do
|
||||
describe "options validation" do
|
||||
let(:valid_options) { {context: {}} }
|
||||
|
||||
it "does not raise error on valid options" do
|
||||
expect { decorator_class.new(source, valid_options) }.to_not raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Invalid option keys: :foo')
|
||||
end
|
||||
end
|
||||
|
||||
it "sets the source" do
|
||||
subject.source.should be source
|
||||
end
|
||||
|
||||
it "stores options" do
|
||||
decorator = decorator_class.new(source, some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "stores options internally" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
decorator.send(:options).should == {context: {some: 'context'}}
|
||||
end
|
||||
|
||||
it "stores context" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
|
||||
context "when decorating an instance of itself" do
|
||||
|
@ -24,15 +41,15 @@ describe Draper::Decorator do
|
|||
|
||||
context "when options are supplied" do
|
||||
it "overwrites existing options" do
|
||||
decorator = ProductDecorator.new(source, role: :admin)
|
||||
ProductDecorator.new(decorator, role: :user).options.should == {role: :user}
|
||||
decorator = ProductDecorator.new(source, context: {role: :admin})
|
||||
ProductDecorator.new(decorator, context: {role: :user}).send(:options).should == {context: {role: :user}}
|
||||
end
|
||||
end
|
||||
|
||||
context "when no options are supplied" do
|
||||
it "preserves existing options" do
|
||||
decorator = ProductDecorator.new(source, role: :admin)
|
||||
ProductDecorator.new(decorator).options.should == {role: :admin}
|
||||
decorator = ProductDecorator.new(source, context: {role: :admin})
|
||||
ProductDecorator.new(decorator).send(:options).should == {context: {role: :admin}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -55,10 +72,51 @@ describe Draper::Decorator do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#options" do
|
||||
it "blocks options externally" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
expect { decorator.options }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#options=" do
|
||||
it "permits modification of options internally" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
decorator.send(:options=, {context: {some: 'other_context'}})
|
||||
decorator.send(:options).should == {context: {some: 'other_context'}}
|
||||
end
|
||||
|
||||
it "blocks options= externally" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
expect { decorator.options = {context: {some: 'other_context'}} }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#context=" do
|
||||
it "permits modification of context" do
|
||||
decorator = decorator_class.new(source, context: {some: 'context'})
|
||||
decorator.context = {some: 'other_context'}
|
||||
decorator.context.should == {some: 'other_context'}
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decorate_collection" do
|
||||
subject { ProductDecorator.decorate_collection(source) }
|
||||
let(:source) { [Product.new, Widget.new] }
|
||||
|
||||
describe "options validation" do
|
||||
let(:valid_options) { {with: :infer, context: {}} }
|
||||
before(:each) { Draper::CollectionDecorator.stub(:new) }
|
||||
|
||||
it "does not raise error on valid options" do
|
||||
expect { ProductDecorator.decorate_collection(source, valid_options) }.to_not raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Invalid option keys: :foo')
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a collection decorator" do
|
||||
subject.should be_a Draper::CollectionDecorator
|
||||
subject.source.should be source
|
||||
|
@ -77,11 +135,11 @@ describe Draper::Decorator do
|
|||
end
|
||||
end
|
||||
|
||||
context "with options" do
|
||||
subject { ProductDecorator.decorate_collection(source, with: :infer, some: "options") }
|
||||
context "with context" do
|
||||
subject { ProductDecorator.decorate_collection(source, with: :infer, context: {some: 'context'}) }
|
||||
|
||||
it "passes the options to the collection decorator" do
|
||||
subject.options.should == {some: "options"}
|
||||
it "passes the context to the collection decorator" do
|
||||
subject.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -104,14 +162,14 @@ describe Draper::Decorator do
|
|||
end
|
||||
|
||||
describe "#localize" do
|
||||
before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
|
||||
before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
|
||||
|
||||
it "delegates to #helpers" do
|
||||
subject.localize(:an_object, some: "options")
|
||||
subject.localize(:an_object, some: 'parameter')
|
||||
end
|
||||
|
||||
it "is aliased to #l" do
|
||||
subject.l(:an_object, some: "options")
|
||||
subject.l(:an_object, some: 'parameter')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -223,8 +281,26 @@ describe Draper::Decorator do
|
|||
describe "overridden association method" do
|
||||
let(:decorated_association) { ->{} }
|
||||
|
||||
describe "options validation" do
|
||||
let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
|
||||
before(:each) { Draper::DecoratedAssociation.stub(:new).and_return(decorated_association) }
|
||||
|
||||
it "does not raise error on valid options" do
|
||||
expect { decorator_class.decorates_association :similar_products, valid_options }.to_not raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, 'Invalid option keys: :foo')
|
||||
end
|
||||
end
|
||||
|
||||
it "creates a DecoratedAssociation" do
|
||||
Draper::DecoratedAssociation.should_receive(:new).with(source, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
|
||||
Draper::DecoratedAssociation.should_receive(:new).with(subject, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
|
||||
subject.similar_products
|
||||
end
|
||||
|
||||
it "receives the Decorator" do
|
||||
Draper::DecoratedAssociation.should_receive(:new).with(kind_of(decorator_class), :similar_products, {with: ProductDecorator}).and_return(decorated_association)
|
||||
subject.similar_products
|
||||
end
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ describe Draper::Finders do
|
|||
decorator.source.should be found
|
||||
end
|
||||
|
||||
it "passes options to the decorator" do
|
||||
decorator = ProductDecorator.find(1, some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "passes context to the decorator" do
|
||||
decorator = ProductDecorator.find(1, context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -55,10 +55,10 @@ describe Draper::Finders do
|
|||
ProductDecorator.find_or_create_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "passes options to the decorator" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", {some: "options"})
|
||||
decorator = ProductDecorator.find_by_name_and_size("apples", "large", some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "passes context to the decorator" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", context: {some: 'context'})
|
||||
decorator = ProductDecorator.find_by_name_and_size("apples", "large", context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -84,9 +84,9 @@ describe Draper::Finders do
|
|||
collection.first.should be_a ProductDecorator
|
||||
end
|
||||
|
||||
it "passes options to the collection decorator" do
|
||||
collection = ProductDecorator.all(some: "options")
|
||||
collection.options.should == {some: "options"}
|
||||
it "passes context to the collection decorator" do
|
||||
collection = ProductDecorator.all(context: {some: 'context'})
|
||||
collection.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -104,9 +104,9 @@ describe Draper::Finders do
|
|||
decorator.source.should be first
|
||||
end
|
||||
|
||||
it "passes options to the decorator" do
|
||||
decorator = ProductDecorator.first(some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "passes context to the decorator" do
|
||||
decorator = ProductDecorator.first(context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,9 +124,9 @@ describe Draper::Finders do
|
|||
decorator.source.should be last
|
||||
end
|
||||
|
||||
it "passes options to the decorator" do
|
||||
decorator = ProductDecorator.last(some: "options")
|
||||
decorator.options.should == {some: "options"}
|
||||
it "passes context to the decorator" do
|
||||
decorator = ProductDecorator.last(context: {some: 'context'})
|
||||
decorator.context.should == {some: 'context'}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue