Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use current module's namespace for decorates_association #835

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions lib/draper/decoratable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ module Decoratable
# @param [Hash] options
# see {Decorator#initialize}
def decorate(options = {})
decorator_class.decorate(self, options)
decorator_class(namespace: options.delete(:namespace)).decorate(self, options)
end

# (see ClassMethods#decorator_class)
def decorator_class
self.class.decorator_class
def decorator_class(namespace: nil)
self.class.decorator_class(namespace: namespace)
end

def decorator_class?
Expand Down Expand Up @@ -55,7 +55,7 @@ module ClassMethods
# @param [Hash] options
# see {Decorator.decorate_collection}.
def decorate(options = {})
decorator_class.decorate_collection(all, options.reverse_merge(with: nil))
decorator_class(namespace: options[:namespace]).decorate_collection(all, options.reverse_merge(with: nil))
end

def decorator_class?
Expand All @@ -68,9 +68,11 @@ def decorator_class?
# `Product` maps to `ProductDecorator`).
#
# @return [Class] the inferred decorator class.
def decorator_class(called_on = self)
def decorator_class(called_on = self, namespace: nil)
prefix = respond_to?(:model_name) ? model_name : name
decorator_name = "#{prefix}Decorator"
namespace = "#{namespace}::" if namespace.present?

decorator_name = "#{namespace}#{prefix}Decorator"
decorator_name_constant = decorator_name.safe_constantize
return decorator_name_constant unless decorator_name_constant.nil?

Expand Down
2 changes: 1 addition & 1 deletion lib/draper/decorated_association.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(owner, association, options)

decorator_class = options[:with]
context = options.fetch(:context, ->(context){ context })
@factory = Draper::Factory.new(with: decorator_class, context: context)
@factory = Draper::Factory.new(with: decorator_class, context: context, namespace: owner.namespace)
end

def call
Expand Down
7 changes: 6 additions & 1 deletion lib/draper/decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ def self.decorates_associations(*associations)
# extra data to be stored in the collection decorator.
def self.decorate_collection(object, options = {})
options.assert_valid_keys(:with, :context)
collection_decorator_class.new(object, options.reverse_merge(with: self))
options[:with] ||= self
collection_decorator_class.new(object, options)
end

# @return [Array<Class>] the list of decorators that have been applied to
Expand Down Expand Up @@ -218,6 +219,10 @@ def attributes
object.attributes.select {|attribute, _| respond_to?(attribute) }
end

def namespace
self.class.to_s.deconstantize.presence
end

# ActiveModel compatibility
delegate :to_param, :to_partial_path

Expand Down
12 changes: 7 additions & 5 deletions lib/draper/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class Factory
# 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)
options.assert_valid_keys(:with, :context, :namespace)
@decorator_class = options.delete(:with)
@namespace = options.delete(:namespace)
@default_options = options
end

Expand All @@ -28,7 +29,7 @@ def initialize(options = {})
# @return [Decorator, CollectionDecorator] the decorated object.
def decorate(object, options = {})
return nil if object.nil?
Worker.new(decorator_class, object).call(options.reverse_merge(default_options))
Worker.new(decorator_class, object, @namespace).call(options.reverse_merge(default_options))
end

private
Expand All @@ -37,9 +38,10 @@ def decorate(object, options = {})

# @private
class Worker
def initialize(decorator_class, object)
def initialize(decorator_class, object, namespace)
@decorator_class = decorator_class
@object = object
@namespace = namespace
end

def call(options)
Expand All @@ -60,9 +62,9 @@ def decorator

def object_decorator
if collection?
->(object, options) { object.decorator_class.decorate_collection(object, options.reverse_merge(with: nil))}
->(object, options) { object.decorator_class(namespace: @namespace).decorate_collection(object, options.reverse_merge(with: nil))}
else
->(object, options) { object.decorate(options) }
->(object, options) { object.decorate(options.merge(namespace: @namespace)) }
end
end

Expand Down
6 changes: 6 additions & 0 deletions spec/draper/decoratable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ module Draper
end
end

context "when namespace is passed explicitly" do
it "returns namespaced decorator class" do
expect(Product.decorator_class(namespace: Namespaced)).to be Namespaced::ProductDecorator
end
end

context "when the decorator contains name error" do
it "throws an NameError" do
# We imitate ActiveSupport::Autoload behavior here in order to cause lazy NameError exception raising
Expand Down
16 changes: 8 additions & 8 deletions spec/draper/decorated_association_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ module Draper
it "creates a factory" do
options = {with: Decorator, context: {foo: "bar"}}

expect(Factory).to receive(:new).with(options)
DecoratedAssociation.new(double, :association, options)
expect(Factory).to receive(:new).with(options.merge(namespace: nil))
DecoratedAssociation.new(double(namespace: nil), :association, options)
end

describe ":with option" do
it "defaults to nil" do
expect(Factory).to receive(:new).with(with: nil, context: anything())
DecoratedAssociation.new(double, :association, {})
expect(Factory).to receive(:new).with(with: nil, context: anything(), namespace: nil)
DecoratedAssociation.new(double(namespace: nil), :association, {})
end
end

Expand All @@ -31,7 +31,7 @@ module Draper
expect(Factory).to receive(:new) do |options|
options[:context].call(:anything) == :anything
end
DecoratedAssociation.new(double, :association, {})
DecoratedAssociation.new(double(namespace: nil), :association, {})
end
end
end
Expand All @@ -43,7 +43,7 @@ module Draper
associated = double
owner_context = {foo: "bar"}
object = double(association: associated)
owner = double(object: object, context: owner_context)
owner = double(object: object, context: owner_context, namespace: nil)
decorated_association = DecoratedAssociation.new(owner, :association, {})
decorated = double

Expand All @@ -54,7 +54,7 @@ module Draper
it "memoizes" do
factory = double
allow(Factory).to receive_messages(new: factory)
owner = double(object: double(association: double), context: {})
owner = double(object: double(association: double), context: {}, namespace: nil)
decorated_association = DecoratedAssociation.new(owner, :association, {})
decorated = double

Expand All @@ -69,7 +69,7 @@ module Draper
allow(Factory).to receive_messages(new: factory)
scoped = double
object = double(association: double(applied_scope: scoped))
owner = double(object: object, context: {})
owner = double(object: object, context: {}, namespace: nil)
decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
decorated = double

Expand Down
15 changes: 15 additions & 0 deletions spec/draper/decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,21 @@ module Draper
end
end

describe "#namespace" do
it "returns own module nesting" do
decorator = Namespaced::ProductDecorator.new(double)
expect(decorator.namespace).to eq("Namespaced")
end

context "when class has no nesting" do
it "returns nil" do
::TopLevelDecorator = Class.new(Draper::Decorator)
decorator = TopLevelDecorator.new(double)
expect(decorator.namespace).to eq(nil)
end
end
end

describe ".model_name" do
it "delegates to the object class" do
allow(Decorator).to receive(:object_class).and_return(double(model_name: :delegated))
Expand Down
36 changes: 18 additions & 18 deletions spec/draper/factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module Draper
factory = Factory.new
object = double

expect(Factory::Worker).to receive(:new).with(anything(), object).and_return(->(*){})
expect(Factory::Worker).to receive(:new).with(anything(), object, anything()).and_return(->(*){})
factory.decorate(object)
end

Expand All @@ -43,7 +43,7 @@ module Draper
decorator_class = double
factory = Factory.new(with: decorator_class)

expect(Factory::Worker).to receive(:new).with(decorator_class, anything()).and_return(->(*){})
expect(Factory::Worker).to receive(:new).with(decorator_class, anything(), anything()).and_return(->(*){})
factory.decorate(double)
end
end
Expand All @@ -52,7 +52,7 @@ module Draper
it "passes nil to the worker" do
factory = Factory.new

expect(Factory::Worker).to receive(:new).with(nil, anything()).and_return(->(*){})
expect(Factory::Worker).to receive(:new).with(nil, anything(), anything()).and_return(->(*){})
factory.decorate(double)
end
end
Expand Down Expand Up @@ -94,7 +94,7 @@ module Draper
it "calls the decorator method" do
object = double
options = {foo: "bar"}
worker = Factory::Worker.new(double, object)
worker = Factory::Worker.new(double, object, nil)
decorator = ->(*){}
allow(worker).to receive(:decorator){ decorator }

Expand All @@ -104,7 +104,7 @@ module Draper

context "when the :context option is callable" do
it "calls it" do
worker = Factory::Worker.new(double, double)
worker = Factory::Worker.new(double, double, nil)
decorator = ->(*){}
allow(worker).to receive_messages decorator: decorator
context = {foo: "bar"}
Expand All @@ -114,7 +114,7 @@ module Draper
end

it "receives arguments from the :context_args option" do
worker = Factory::Worker.new(double, double)
worker = Factory::Worker.new(double, double, nil)
allow(worker).to receive_messages decorator: ->(*){}
context = ->{}

Expand All @@ -123,7 +123,7 @@ module Draper
end

it "wraps non-arrays passed to :context_args" do
worker = Factory::Worker.new(double, double)
worker = Factory::Worker.new(double, double, nil)
allow(worker).to receive_messages decorator: ->(*){}
context = ->{}
hash = {foo: "bar"}
Expand All @@ -135,7 +135,7 @@ module Draper

context "when the :context option is not callable" do
it "doesn't call it" do
worker = Factory::Worker.new(double, double)
worker = Factory::Worker.new(double, double, nil)
decorator = ->(*){}
allow(worker).to receive_messages decorator: decorator
context = {foo: "bar"}
Expand All @@ -146,7 +146,7 @@ module Draper
end

it "does not pass the :context_args option to the decorator" do
worker = Factory::Worker.new(double, double)
worker = Factory::Worker.new(double, double, nil)
decorator = ->(*){}
allow(worker).to receive_messages decorator: decorator

Expand All @@ -160,7 +160,7 @@ module Draper
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)
worker = Factory::Worker.new(decorator_class, double, nil)

expect(worker.decorator).to eq decorator_class.method(:decorate)
end
Expand All @@ -171,17 +171,17 @@ module Draper
it "returns the object's #decorate method" do
object = double
options = {foo: "bar"}
worker = Factory::Worker.new(nil, object)
worker = Factory::Worker.new(nil, object, nil)

expect(object).to receive(:decorate).with(options).and_return(:decorated)
expect(object).to receive(:decorate).with(options.merge(namespace: nil)).and_return(:decorated)
expect(worker.decorator.call(object, options)).to be :decorated
end
end

context "and the object is not decoratable" do
it "raises an error" do
object = double
worker = Factory::Worker.new(nil, object)
worker = Factory::Worker.new(nil, object, nil)

expect{worker.decorator}.to raise_error UninferrableDecoratorError
end
Expand All @@ -193,7 +193,7 @@ module Draper
object = Struct.new(:stuff).new("things")

decorator_class = Class.new(Decorator)
worker = Factory::Worker.new(decorator_class, object)
worker = Factory::Worker.new(decorator_class, object, nil)

expect(worker.decorator).to eq decorator_class.method(:decorate)
end
Expand All @@ -204,7 +204,7 @@ module Draper
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, [])
worker = Factory::Worker.new(decorator_class, [], nil)

expect(worker.decorator).to eq decorator_class.method(:decorate)
end
Expand All @@ -213,7 +213,7 @@ module Draper
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, [])
worker = Factory::Worker.new(decorator_class, [], nil)

expect(worker.decorator).to eq decorator_class.method(:decorate_collection)
end
Expand All @@ -226,7 +226,7 @@ module Draper
decorator_class = Class.new(Decorator)
allow(object).to receive(:decorator_class){ decorator_class }
allow(object).to receive(:decorate){ nil }
worker = Factory::Worker.new(nil, object)
worker = Factory::Worker.new(nil, object, nil)

expect(decorator_class).to receive(:decorate_collection).with(object, {foo: "bar", with: nil}).and_return(:decorated)
expect(worker.decorator.call(object, foo: "bar")).to be :decorated
Expand All @@ -235,7 +235,7 @@ module Draper

context "and the object is not decoratable" do
it "returns the .decorate method from CollectionDecorator" do
worker = Factory::Worker.new(nil, [])
worker = Factory::Worker.new(nil, [], nil)

expect(worker.decorator).to eq CollectionDecorator.method(:decorate)
end
Expand Down