mirror of
https://github.com/drapergem/draper
synced 2023-03-27 23:21:17 -04:00
Extract Factory from DecoratedAssociation
This commit is contained in:
parent
bbcc35cd8b
commit
a6a60bcf92
4 changed files with 260 additions and 44 deletions
|
@ -16,6 +16,7 @@ require 'draper/decorator'
|
|||
require 'draper/helper_proxy'
|
||||
require 'draper/lazy_helpers'
|
||||
require 'draper/decoratable'
|
||||
require 'draper/factory'
|
||||
require 'draper/decorated_association'
|
||||
require 'draper/helper_support'
|
||||
require 'draper/view_context'
|
||||
|
|
|
@ -8,14 +8,15 @@ module Draper
|
|||
@owner = owner
|
||||
@association = association
|
||||
|
||||
@decorator_class = options[:with]
|
||||
@scope = options[:scope]
|
||||
@context = options.fetch(:context, owner.context)
|
||||
|
||||
@factory = Draper::Factory.new(options.slice(:with))
|
||||
end
|
||||
|
||||
def call
|
||||
return undecorated if undecorated.nil?
|
||||
decorated
|
||||
decorate unless defined?(@decorated)
|
||||
@decorated
|
||||
end
|
||||
|
||||
def context
|
||||
|
@ -25,49 +26,13 @@ module Draper
|
|||
|
||||
private
|
||||
|
||||
attr_reader :owner, :association, :scope
|
||||
attr_reader :factory, :owner, :association, :scope
|
||||
|
||||
def source
|
||||
owner.source
|
||||
end
|
||||
def decorate
|
||||
associated = owner.source.send(association)
|
||||
associated = associated.send(scope) if scope
|
||||
|
||||
def undecorated
|
||||
@undecorated ||= begin
|
||||
associated = source.send(association)
|
||||
associated = associated.send(scope) if scope
|
||||
associated
|
||||
end
|
||||
end
|
||||
|
||||
def decorated
|
||||
@decorated ||= decorator.call(undecorated, context: context)
|
||||
end
|
||||
|
||||
def collection?
|
||||
undecorated.respond_to?(:first)
|
||||
end
|
||||
|
||||
def decorator
|
||||
return collection_decorator if collection?
|
||||
decorator_class.method(:decorate)
|
||||
end
|
||||
|
||||
def collection_decorator
|
||||
klass = decorator_class || Draper::CollectionDecorator
|
||||
|
||||
if klass.respond_to?(:decorate_collection)
|
||||
klass.method(:decorate_collection)
|
||||
else
|
||||
klass.method(:decorate)
|
||||
end
|
||||
end
|
||||
|
||||
def decorator_class
|
||||
@decorator_class || inferred_decorator_class
|
||||
end
|
||||
|
||||
def inferred_decorator_class
|
||||
undecorated.decorator_class if undecorated.respond_to?(:decorator_class)
|
||||
@decorated = factory.decorate(associated, context: context)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
76
lib/draper/factory.rb
Normal file
76
lib/draper/factory.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
module Draper
|
||||
class Factory
|
||||
# Creates a decorator factory.
|
||||
#
|
||||
# @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.
|
||||
def initialize(options = {})
|
||||
options.assert_valid_keys(:with, :context)
|
||||
@decorator_class = options.delete(:with)
|
||||
@default_options = options
|
||||
end
|
||||
|
||||
# Decorates an object, inferring whether to create a singular or collection
|
||||
# decorator from the type of object passed.
|
||||
#
|
||||
# @param [Object] source
|
||||
# object to decorate.
|
||||
# @option options [Hash] context
|
||||
# extra data to be stored in the decorator. Overrides any context passed
|
||||
# to the constructor.
|
||||
# @return [Decorator, CollectionDecorator] the decorated object.
|
||||
def decorate(source, options = {})
|
||||
return nil if source.nil?
|
||||
Worker.new(decorator_class, source).call(options.reverse_merge(default_options))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :decorator_class, :default_options
|
||||
|
||||
class Worker
|
||||
def initialize(decorator_class, source)
|
||||
@decorator_class = decorator_class
|
||||
@source = source
|
||||
end
|
||||
|
||||
def call(options)
|
||||
decorator.call(source, options)
|
||||
end
|
||||
|
||||
def decorator
|
||||
return collection_decorator if collection?
|
||||
decorator_class.method(:decorate)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :source
|
||||
|
||||
def collection_decorator
|
||||
klass = decorator_class || Draper::CollectionDecorator
|
||||
|
||||
if klass.respond_to?(:decorate_collection)
|
||||
klass.method(:decorate_collection)
|
||||
else
|
||||
klass.method(:decorate)
|
||||
end
|
||||
end
|
||||
|
||||
def collection?
|
||||
source.respond_to?(:first)
|
||||
end
|
||||
|
||||
def decorator_class
|
||||
@decorator_class || source_decorator_class
|
||||
end
|
||||
|
||||
def source_decorator_class
|
||||
source.decorator_class if source.respond_to?(:decorator_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
174
spec/draper/factory_spec.rb
Normal file
174
spec/draper/factory_spec.rb
Normal file
|
@ -0,0 +1,174 @@
|
|||
require 'spec_helper'
|
||||
|
||||
module Draper
|
||||
describe Factory do
|
||||
|
||||
describe "#initialize" do
|
||||
it "accepts valid options" do
|
||||
valid_options = {with: Decorator, context: {foo: "bar"}}
|
||||
expect{Factory.new(valid_options)}.not_to raise_error
|
||||
end
|
||||
|
||||
it "rejects invalid options" do
|
||||
expect{Factory.new(foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorate" do
|
||||
context "when source is nil" do
|
||||
it "returns nil" do
|
||||
factory = Factory.new
|
||||
|
||||
expect(factory.decorate(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "calls a worker" do
|
||||
factory = Factory.new
|
||||
worker = ->(*){ :decorated }
|
||||
|
||||
Factory::Worker.should_receive(:new).and_return(worker)
|
||||
expect(factory.decorate(double)).to be :decorated
|
||||
end
|
||||
|
||||
it "passes the source to the worker" do
|
||||
factory = Factory.new
|
||||
source = double
|
||||
|
||||
Factory::Worker.should_receive(:new).with(anything(), source).and_return(->(*){})
|
||||
factory.decorate(source)
|
||||
end
|
||||
|
||||
context "when the :with option was given" do
|
||||
it "passes the decorator class to the worker" do
|
||||
decorator_class = double
|
||||
factory = Factory.new(with: decorator_class)
|
||||
|
||||
Factory::Worker.should_receive(:new).with(decorator_class, anything()).and_return(->(*){})
|
||||
factory.decorate(double)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the :with option was omitted" do
|
||||
it "passes nil to the worker" do
|
||||
factory = Factory.new
|
||||
|
||||
Factory::Worker.should_receive(:new).with(nil, anything()).and_return(->(*){})
|
||||
factory.decorate(double)
|
||||
end
|
||||
end
|
||||
|
||||
it "passes options to the call" do
|
||||
factory = Factory.new
|
||||
worker = ->(*){}
|
||||
Factory::Worker.stub new: worker
|
||||
options = {foo: "bar"}
|
||||
|
||||
worker.should_receive(:call).with(options)
|
||||
factory.decorate(double, options)
|
||||
end
|
||||
|
||||
context "when the :context option was given" do
|
||||
it "sets the passed context" do
|
||||
factory = Factory.new(context: {foo: "bar"})
|
||||
worker = ->(*){}
|
||||
Factory::Worker.stub new: worker
|
||||
|
||||
worker.should_receive(:call).with(baz: "qux", context: {foo: "bar"})
|
||||
factory.decorate(double, {baz: "qux"})
|
||||
end
|
||||
|
||||
it "is overridden by explicitly-specified context" do
|
||||
factory = Factory.new(context: {foo: "bar"})
|
||||
worker = ->(*){}
|
||||
Factory::Worker.stub new: worker
|
||||
|
||||
worker.should_receive(:call).with(context: {baz: "qux"})
|
||||
factory.decorate(double, context: {baz: "qux"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe Factory::Worker do
|
||||
|
||||
describe "#call" do
|
||||
it "calls the decorator method" do
|
||||
source = double
|
||||
options = {foo: "bar"}
|
||||
worker = Factory::Worker.new(double, source)
|
||||
decorator = ->(*){}
|
||||
worker.stub decorator: decorator
|
||||
|
||||
decorator.should_receive(:call).with(source, options).and_return(:decorated)
|
||||
expect(worker.call(options)).to be :decorated
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorator" do
|
||||
context "for a singular source" do
|
||||
context "when decorator_class is specified" do
|
||||
it "returns the .decorate method from the decorator" do
|
||||
decorator_class = Class.new(Decorator)
|
||||
worker = Factory::Worker.new(decorator_class, double)
|
||||
|
||||
expect(worker.decorator).to eq decorator_class.method(:decorate)
|
||||
end
|
||||
end
|
||||
|
||||
context "when decorator_class is unspecified" do
|
||||
it "returns the .decorate method from the source's decorator" do
|
||||
decorator_class = Class.new(Decorator)
|
||||
source = double(decorator_class: decorator_class)
|
||||
worker = Factory::Worker.new(nil, source)
|
||||
|
||||
expect(worker.decorator).to eq decorator_class.method(:decorate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for a collection source" do
|
||||
context "when decorator_class is a CollectionDecorator" do
|
||||
it "returns the .decorate method from the collection decorator" do
|
||||
decorator_class = Class.new(CollectionDecorator)
|
||||
worker = Factory::Worker.new(decorator_class, [])
|
||||
|
||||
expect(worker.decorator).to eq decorator_class.method(:decorate)
|
||||
end
|
||||
end
|
||||
|
||||
context "when decorator_class is a Decorator" do
|
||||
it "returns the .decorate_collection method from the decorator" do
|
||||
decorator_class = Class.new(Decorator)
|
||||
worker = Factory::Worker.new(decorator_class, [])
|
||||
|
||||
expect(worker.decorator).to eq decorator_class.method(:decorate_collection)
|
||||
end
|
||||
end
|
||||
|
||||
context "when decorator_class is unspecified" do
|
||||
context "and the source is decoratable" do
|
||||
it "returns the .decorate method from the source's decorator" do
|
||||
decorator_class = Class.new(CollectionDecorator)
|
||||
source = []
|
||||
source.stub decorator_class: decorator_class
|
||||
worker = Factory::Worker.new(nil, source)
|
||||
|
||||
expect(worker.decorator).to eq decorator_class.method(:decorate)
|
||||
end
|
||||
end
|
||||
|
||||
context "and the source is not decoratable" do
|
||||
it "returns the .decorate method from CollectionDecorator" do
|
||||
worker = Factory::Worker.new(nil, [])
|
||||
|
||||
expect(worker.decorator).to eq CollectionDecorator.method(:decorate)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue