Blow up Decoratable
This commit is contained in:
parent
57a514133b
commit
f4b21284f4
|
@ -16,7 +16,6 @@ require 'draper/finders'
|
|||
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'
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
require 'draper/decoratable/equality'
|
||||
|
||||
module Draper
|
||||
# Provides shortcuts to decorate objects directly, so you can do
|
||||
# `@product.decorate` instead of `ProductDecorator.new(@product)`.
|
||||
#
|
||||
# This module is included by default into `ActiveRecord::Base` and
|
||||
# `Mongoid::Document`, but you're using another ORM, or want to decorate
|
||||
# plain old Ruby objects, you can include it manually.
|
||||
module Decoratable
|
||||
extend ActiveSupport::Concern
|
||||
include Draper::Decoratable::Equality
|
||||
|
||||
# Decorates the object using the inferred {#decorator_class}.
|
||||
# @param [Hash] options
|
||||
# see {Decorator#initialize}
|
||||
def decorate(options = {})
|
||||
decorator_class.decorate(self, options)
|
||||
end
|
||||
|
||||
# (see ClassMethods#decorator_class)
|
||||
def decorator_class
|
||||
self.class.decorator_class
|
||||
end
|
||||
|
||||
def decorator_class?
|
||||
self.class.decorator_class?
|
||||
end
|
||||
|
||||
# The list of decorators that have been applied to the object.
|
||||
#
|
||||
# @return [Array<Class>] `[]`
|
||||
def applied_decorators
|
||||
[]
|
||||
end
|
||||
|
||||
# (see Decorator#decorated_with?)
|
||||
# @return [false]
|
||||
def decorated_with?(decorator_class)
|
||||
false
|
||||
end
|
||||
|
||||
# Checks if this object is decorated.
|
||||
#
|
||||
# @return [false]
|
||||
def decorated?
|
||||
false
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Decorates a collection of objects. Used at the end of a scope chain.
|
||||
#
|
||||
# @example
|
||||
# Product.popular.decorate
|
||||
# @param [Hash] options
|
||||
# see {Decorator.decorate_collection}.
|
||||
def decorate(options = {})
|
||||
collection = Rails::VERSION::MAJOR >= 4 ? all : scoped
|
||||
decorator_class.decorate_collection(collection, options.reverse_merge(with: nil))
|
||||
end
|
||||
|
||||
def decorator_class?
|
||||
decorator_class
|
||||
rescue Draper::UninferrableDecoratorError
|
||||
false
|
||||
end
|
||||
|
||||
# Infers the decorator class to be used by {Decoratable#decorate} (e.g.
|
||||
# `Product` maps to `ProductDecorator`).
|
||||
#
|
||||
# @return [Class] the inferred decorator class.
|
||||
def decorator_class
|
||||
prefix = respond_to?(:model_name) ? model_name : name
|
||||
decorator_name = "#{prefix}Decorator"
|
||||
decorator_name.constantize
|
||||
rescue NameError => error
|
||||
if superclass.respond_to?(:decorator_class)
|
||||
superclass.decorator_class
|
||||
else
|
||||
raise unless error.missing_name?(decorator_name)
|
||||
raise Draper::UninferrableDecoratorError.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
# Compares with possibly-decorated objects.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def ===(other)
|
||||
super || (other.respond_to?(:object) && super(other.object))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'support/shared_examples/decoratable_equality'
|
||||
|
||||
module Draper
|
||||
describe Decoratable::Equality do
|
||||
describe "#==" do
|
||||
it_behaves_like "decoration-aware #==", Object.new.extend(Decoratable::Equality)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,202 +0,0 @@
|
|||
require 'spec_helper'
|
||||
require 'support/shared_examples/decoratable_equality'
|
||||
|
||||
module Draper
|
||||
describe Decoratable do
|
||||
|
||||
describe "#decorate" do
|
||||
it "returns a decorator for self" do
|
||||
product = Product.new
|
||||
decorator = product.decorate
|
||||
|
||||
expect(decorator).to be_a ProductDecorator
|
||||
expect(decorator.object).to be product
|
||||
end
|
||||
|
||||
it "accepts context" do
|
||||
context = {some: "context"}
|
||||
decorator = Product.new.decorate(context: context)
|
||||
|
||||
expect(decorator.context).to be context
|
||||
end
|
||||
|
||||
it "uses the #decorator_class" do
|
||||
product = Product.new
|
||||
product.stub decorator_class: OtherDecorator
|
||||
|
||||
expect(product.decorate).to be_an_instance_of OtherDecorator
|
||||
end
|
||||
end
|
||||
|
||||
describe "#applied_decorators" do
|
||||
it "returns an empty list" do
|
||||
expect(Product.new.applied_decorators).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorated_with?" do
|
||||
it "returns false" do
|
||||
expect(Product.new).not_to be_decorated_with Decorator
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorated?" do
|
||||
it "returns false" do
|
||||
expect(Product.new).not_to be_decorated
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorator_class?" do
|
||||
it "returns true for decoratable model" do
|
||||
expect(Product.new.decorator_class?).to be_truthy
|
||||
end
|
||||
|
||||
it "returns false for non-decoratable model" do
|
||||
expect(Model.new.decorator_class?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decorator_class?" do
|
||||
it "returns true for decoratable model" do
|
||||
expect(Product.decorator_class?).to be_truthy
|
||||
end
|
||||
|
||||
it "returns false for non-decoratable model" do
|
||||
expect(Model.decorator_class?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "#decorator_class" do
|
||||
it "delegates to .decorator_class" do
|
||||
product = Product.new
|
||||
|
||||
Product.should_receive(:decorator_class).and_return(:some_decorator)
|
||||
expect(product.decorator_class).to be :some_decorator
|
||||
end
|
||||
end
|
||||
|
||||
describe "#==" do
|
||||
it_behaves_like "decoration-aware #==", Product.new
|
||||
end
|
||||
|
||||
describe "#===" do
|
||||
it "is true when #== is true" do
|
||||
product = Product.new
|
||||
|
||||
product.should_receive(:==).and_return(true)
|
||||
expect(product === :anything).to be_truthy
|
||||
end
|
||||
|
||||
it "is false when #== is false" do
|
||||
product = Product.new
|
||||
|
||||
product.should_receive(:==).and_return(false)
|
||||
expect(product === :anything).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe ".====" do
|
||||
it "is true for an instance" do
|
||||
expect(Product === Product.new).to be_truthy
|
||||
end
|
||||
|
||||
it "is true for a derived instance" do
|
||||
expect(Product === Class.new(Product).new).to be_truthy
|
||||
end
|
||||
|
||||
it "is false for an unrelated instance" do
|
||||
expect(Product === Model.new).to be_falsey
|
||||
end
|
||||
|
||||
it "is true for a decorated instance" do
|
||||
decorator = double(object: Product.new)
|
||||
|
||||
expect(Product === decorator).to be_truthy
|
||||
end
|
||||
|
||||
it "is true for a decorated derived instance" do
|
||||
decorator = double(object: Class.new(Product).new)
|
||||
|
||||
expect(Product === decorator).to be_truthy
|
||||
end
|
||||
|
||||
it "is false for a decorated unrelated instance" do
|
||||
decorator = double(object: Model.new)
|
||||
|
||||
expect(Product === decorator).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decorate" do
|
||||
let(:scoping_method) { Rails::VERSION::MAJOR >= 4 ? :all : :scoped }
|
||||
|
||||
it "calls #decorate_collection on .decorator_class" do
|
||||
scoped = [Product.new]
|
||||
Product.stub scoping_method => scoped
|
||||
|
||||
Product.decorator_class.should_receive(:decorate_collection).with(scoped, with: nil).and_return(:decorated_collection)
|
||||
expect(Product.decorate).to be :decorated_collection
|
||||
end
|
||||
|
||||
it "accepts options" do
|
||||
options = {with: ProductDecorator, context: {some: "context"}}
|
||||
Product.stub scoping_method => []
|
||||
|
||||
Product.decorator_class.should_receive(:decorate_collection).with([], options)
|
||||
Product.decorate(options)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decorator_class" do
|
||||
context "for classes" do
|
||||
it "infers the decorator from the class" do
|
||||
expect(Product.decorator_class).to be ProductDecorator
|
||||
end
|
||||
|
||||
context "without a decorator on its own" do
|
||||
it "infers the decorator from a superclass" do
|
||||
expect(SpecialProduct.decorator_class).to be ProductDecorator
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for ActiveModel classes" do
|
||||
it "infers the decorator from the model name" do
|
||||
allow(Product).to receive(:model_name){"Other"}
|
||||
|
||||
expect(Product.decorator_class).to be OtherDecorator
|
||||
end
|
||||
end
|
||||
|
||||
context "in a namespace" do
|
||||
context "for classes" do
|
||||
it "infers the decorator from the class" do
|
||||
expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator
|
||||
end
|
||||
end
|
||||
|
||||
context "for ActiveModel classes" do
|
||||
it "infers the decorator from the model name" do
|
||||
Namespaced::Product.stub(:model_name).and_return("Namespaced::Other")
|
||||
|
||||
expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the decorator can't be inferred" do
|
||||
it "throws an UninferrableDecoratorError" do
|
||||
expect{Model.decorator_class}.to raise_error UninferrableDecoratorError
|
||||
end
|
||||
end
|
||||
|
||||
context "when an unrelated NameError is thrown" do
|
||||
it "re-raises that error" do
|
||||
String.any_instance.stub(:constantize) { Draper::Base }
|
||||
expect{Product.decorator_class}.to raise_error NameError, /Draper::Base/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@ RSpec.configure do |config|
|
|||
end
|
||||
end
|
||||
|
||||
class Model; include Draper::Decoratable; end
|
||||
class Model; end
|
||||
|
||||
class Product < Model; end
|
||||
class SpecialProduct < Product; end
|
||||
|
|
Loading…
Reference in New Issue