parent
0799cd4beb
commit
668661f13a
|
@ -9,9 +9,10 @@ module Draper
|
|||
@association = association
|
||||
|
||||
@scope = options[:scope]
|
||||
@context = options.fetch(:context, ->(context){ context })
|
||||
|
||||
@factory = Draper::Factory.new(options.slice(:with))
|
||||
decorator_class = options[:with]
|
||||
context = options.fetch(:context, ->(context){ context })
|
||||
@factory = Draper::Factory.new(with: decorator_class, context: context)
|
||||
end
|
||||
|
||||
def call
|
||||
|
@ -19,11 +20,6 @@ module Draper
|
|||
@decorated
|
||||
end
|
||||
|
||||
def context
|
||||
return @context.call(owner.context) if @context.respond_to?(:call)
|
||||
@context
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :factory, :owner, :association, :scope
|
||||
|
@ -32,7 +28,7 @@ module Draper
|
|||
associated = owner.source.send(association)
|
||||
associated = associated.send(scope) if scope
|
||||
|
||||
@decorated = factory.decorate(associated, context: context)
|
||||
@decorated = factory.decorate(associated, context_args: owner.context)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -19,7 +19,12 @@ module Draper
|
|||
# @param [Symbols*] variables
|
||||
# names of the instance variables to decorate (without the `@`).
|
||||
# @param [Hash] options
|
||||
# see {Factory#initialize}
|
||||
# @option options [Decorator, CollectionDecorator] :with (nil)
|
||||
# decorator class to use. If nil, it is inferred from the instance
|
||||
# variable.
|
||||
# @option options [Hash, #call] :context
|
||||
# extra data to be stored in the decorator. If a Proc is given, it will
|
||||
# be passed the controller and should return a new context hash.
|
||||
def decorates_assigned(*variables)
|
||||
factory = Draper::Factory.new(variables.extract_options!)
|
||||
|
||||
|
@ -29,7 +34,7 @@ module Draper
|
|||
|
||||
define_method variable do
|
||||
return instance_variable_get(decorated) if instance_variable_defined?(decorated)
|
||||
instance_variable_set decorated, factory.decorate(instance_variable_get(undecorated))
|
||||
instance_variable_set decorated, factory.decorate(instance_variable_get(undecorated), context_args: self)
|
||||
end
|
||||
|
||||
helper_method variable
|
||||
|
|
|
@ -5,8 +5,10 @@ module Draper
|
|||
# @option options [Decorator, CollectionDecorator] :with (nil)
|
||||
# decorator class to use. If nil, it is inferred from the object
|
||||
# passed to {#decorate}.
|
||||
# @option options [Hash] context
|
||||
# extra data to be stored in created decorators.
|
||||
# @option options [Hash, #call] context
|
||||
# extra data to be stored in created decorators. If a proc is given, it
|
||||
# will be called each time {#decorate} is called and its return value
|
||||
# will be used as the context.
|
||||
def initialize(options = {})
|
||||
options.assert_valid_keys(:with, :context)
|
||||
@decorator_class = options.delete(:with)
|
||||
|
@ -21,6 +23,8 @@ module Draper
|
|||
# @option options [Hash] context
|
||||
# extra data to be stored in the decorator. Overrides any context passed
|
||||
# to the constructor.
|
||||
# @option options [Object, Array] context_args (nil)
|
||||
# argument(s) to be passed to the context proc.
|
||||
# @return [Decorator, CollectionDecorator] the decorated object.
|
||||
def decorate(source, options = {})
|
||||
return nil if source.nil?
|
||||
|
@ -31,6 +35,7 @@ module Draper
|
|||
|
||||
attr_reader :decorator_class, :default_options
|
||||
|
||||
# @private
|
||||
class Worker
|
||||
def initialize(decorator_class, source)
|
||||
@decorator_class = decorator_class
|
||||
|
@ -38,6 +43,7 @@ module Draper
|
|||
end
|
||||
|
||||
def call(options)
|
||||
update_context options
|
||||
decorator.call(source, options)
|
||||
end
|
||||
|
||||
|
@ -71,6 +77,11 @@ module Draper
|
|||
def source_decorator_class
|
||||
source.decorator_class if source.respond_to?(:decorator_class)
|
||||
end
|
||||
|
||||
def update_context(options)
|
||||
args = options.delete(:context_args)
|
||||
options[:context] = options[:context].call(*args) if options[:context].respond_to?(:call)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,144 +4,78 @@ module Draper
|
|||
describe DecoratedAssociation do
|
||||
|
||||
describe "#initialize" do
|
||||
describe "options validation" do
|
||||
it "does not raise error on valid options" do
|
||||
it "accepts valid options" do
|
||||
valid_options = {with: Decorator, scope: :foo, context: {}}
|
||||
expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options)}.not_to raise_error
|
||||
end
|
||||
|
||||
it "raises error on invalid options" do
|
||||
it "rejects invalid options" do
|
||||
expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
||||
end
|
||||
|
||||
it "creates a factory" do
|
||||
options = {with: Decorator, context: {foo: "bar"}}
|
||||
|
||||
Factory.should_receive(:new).with(options)
|
||||
DecoratedAssociation.new(double, :association, options)
|
||||
end
|
||||
|
||||
describe ":with option" do
|
||||
it "defaults to nil" do
|
||||
Factory.should_receive(:new).with(with: nil, context: anything())
|
||||
DecoratedAssociation.new(double, :association, {})
|
||||
end
|
||||
end
|
||||
|
||||
describe ":context option" do
|
||||
it "defaults to the identity function" do
|
||||
Factory.should_receive(:new).with do |options|
|
||||
options[:context].call(:anything) == :anything
|
||||
end
|
||||
DecoratedAssociation.new(double, :association, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#call" do
|
||||
let(:context) { {some: "context"} }
|
||||
let(:options) { {} }
|
||||
it "calls the factory" do
|
||||
factory = double
|
||||
Factory.stub new: factory
|
||||
associated = double
|
||||
owner_context = {foo: "bar"}
|
||||
source = double(association: associated)
|
||||
owner = double(source: source, context: owner_context)
|
||||
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
||||
decorated = double
|
||||
|
||||
let(:decorated_association) do
|
||||
owner = double(context: nil, source: double(association: associated))
|
||||
|
||||
DecoratedAssociation.new(owner, :association, options).tap do |decorated_association|
|
||||
decorated_association.stub context: context
|
||||
end
|
||||
factory.should_receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
|
||||
expect(decorated_association.call).to be decorated
|
||||
end
|
||||
|
||||
context "for a singular association" do
|
||||
let(:associated) { Model.new }
|
||||
it "memoizes" do
|
||||
factory = double
|
||||
Factory.stub new: factory
|
||||
owner = double(source: double(association: double), context: {})
|
||||
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
||||
decorated = double
|
||||
|
||||
context "when :with option was given" do
|
||||
let(:options) { {with: Decorator} }
|
||||
|
||||
it "uses the specified decorator" do
|
||||
Decorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
|
||||
expect(decorated_association.call).to be :decorated
|
||||
end
|
||||
factory.should_receive(:decorate).once.and_return(decorated)
|
||||
expect(decorated_association.call).to be decorated
|
||||
expect(decorated_association.call).to be decorated
|
||||
end
|
||||
|
||||
context "when :with option was not given" do
|
||||
it "infers the decorator" do
|
||||
associated.stub decorator_class: OtherDecorator
|
||||
|
||||
OtherDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
|
||||
expect(decorated_association.call).to be :decorated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for a collection association" do
|
||||
let(:associated) { [] }
|
||||
|
||||
context "when :with option is a collection decorator" do
|
||||
let(:options) { {with: ProductsDecorator} }
|
||||
|
||||
it "uses the specified decorator" do
|
||||
ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
||||
expect(decorated_association.call).to be :decorated_collection
|
||||
end
|
||||
end
|
||||
|
||||
context "when :with option is a singular decorator" do
|
||||
let(:options) { {with: ProductDecorator} }
|
||||
|
||||
it "uses a CollectionDecorator of the specified decorator" do
|
||||
ProductDecorator.should_receive(:decorate_collection).with(associated, context: context).and_return(:decorated_collection)
|
||||
expect(decorated_association.call).to be :decorated_collection
|
||||
end
|
||||
end
|
||||
|
||||
context "when :with option was not given" do
|
||||
context "when the collection itself is decoratable" do
|
||||
before { associated.stub decorator_class: ProductsDecorator }
|
||||
|
||||
it "infers the decorator" do
|
||||
ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
||||
expect(decorated_association.call).to be :decorated_collection
|
||||
end
|
||||
end
|
||||
|
||||
context "when the collection is not decoratable" do
|
||||
it "uses a CollectionDecorator of inferred decorators" do
|
||||
CollectionDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
||||
expect(decorated_association.call).to be :decorated_collection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a scope" do
|
||||
let(:options) { {scope: :foo} }
|
||||
let(:associated) { double(foo: scoped) }
|
||||
let(:scoped) { Product.new }
|
||||
|
||||
context "when the :scope option was given" do
|
||||
it "applies the scope before decoration" do
|
||||
expect(decorated_association.call.source).to be scoped
|
||||
end
|
||||
end
|
||||
end
|
||||
factory = double
|
||||
Factory.stub new: factory
|
||||
scoped = double
|
||||
source = double(association: double(applied_scope: scoped))
|
||||
owner = double(source: source, context: {})
|
||||
decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
|
||||
decorated = double
|
||||
|
||||
describe "#context" do
|
||||
let(:owner_context) { {some: "context"} }
|
||||
let(:options) { {} }
|
||||
let(:owner) { double(context: owner_context) }
|
||||
let(:decorated_association) { DecoratedAssociation.new(owner, :association, options) }
|
||||
|
||||
context "when :context option was given" do
|
||||
let(:options) { {context: context} }
|
||||
|
||||
context "and is callable" do
|
||||
let(:context) { ->(*){ :dynamic_context } }
|
||||
|
||||
it "calls it with the owner's context" do
|
||||
context.should_receive(:call).with(owner_context)
|
||||
decorated_association.context
|
||||
end
|
||||
|
||||
it "returns the lambda's return value" do
|
||||
expect(decorated_association.context).to be :dynamic_context
|
||||
end
|
||||
end
|
||||
|
||||
context "and is not callable" do
|
||||
let(:context) { {other: "context"} }
|
||||
|
||||
it "returns the specified value" do
|
||||
expect(decorated_association.context).to be context
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when :context option was not given" do
|
||||
it "returns the owner's context" do
|
||||
expect(decorated_association.context).to be owner_context
|
||||
end
|
||||
|
||||
it "returns the new context if the owner's context changes" do
|
||||
new_context = {other: "context"}
|
||||
owner.stub context: new_context
|
||||
|
||||
expect(decorated_association.context).to be new_context
|
||||
factory.should_receive(:decorate).with(scoped, anything()).and_return(decorated)
|
||||
expect(decorated_association.call).to be decorated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ module Draper
|
|||
controller = controller_class.new
|
||||
controller.instance_variable_set "@article", source
|
||||
|
||||
factory.should_receive(:decorate).with(source).and_return(:decorated)
|
||||
factory.should_receive(:decorate).with(source, context_args: controller).and_return(:decorated)
|
||||
expect(controller.article).to be :decorated
|
||||
end
|
||||
|
||||
|
|
|
@ -104,6 +104,48 @@ module Draper
|
|||
decorator.should_receive(:call).with(source, options).and_return(:decorated)
|
||||
expect(worker.call(options)).to be :decorated
|
||||
end
|
||||
|
||||
context "when the :context option is callable" do
|
||||
it "calls it" do
|
||||
worker = Factory::Worker.new(double, double)
|
||||
decorator = ->(*){}
|
||||
worker.stub decorator: decorator
|
||||
context = {foo: "bar"}
|
||||
|
||||
decorator.should_receive(:call).with(anything(), context: context)
|
||||
worker.call(context: ->{ context })
|
||||
end
|
||||
|
||||
it "receives arguments from the :context_args option" do
|
||||
worker = Factory::Worker.new(double, double)
|
||||
worker.stub decorator: ->(*){}
|
||||
context = ->{}
|
||||
|
||||
context.should_receive(:call).with(:foo, :bar)
|
||||
worker.call(context: context, context_args: [:foo, :bar])
|
||||
end
|
||||
end
|
||||
|
||||
context "when the :context option is not callable" do
|
||||
it "doesn't call it" do
|
||||
worker = Factory::Worker.new(double, double)
|
||||
decorator = ->(*){}
|
||||
worker.stub decorator: decorator
|
||||
context = {foo: "bar"}
|
||||
|
||||
decorator.should_receive(:call).with(anything(), context: context)
|
||||
worker.call(context: context)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not pass the :context_args option to the decorator" do
|
||||
worker = Factory::Worker.new(double, double)
|
||||
decorator = ->(*){}
|
||||
worker.stub decorator: decorator
|
||||
|
||||
decorator.should_receive(:call).with(anything(), foo: "bar")
|
||||
worker.call(foo: "bar", context_args: [])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorator" do
|
||||
|
|
Loading…
Reference in New Issue