Blow up Decoratable

This commit is contained in:
Jeff Casimir 2015-04-10 15:05:22 -06:00
parent 57a514133b
commit f4b21284f4
5 changed files with 1 additions and 310 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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