mirror of
https://github.com/drapergem/draper
synced 2023-03-27 23:21:17 -04:00
Remove .decorates method
This commit is contained in:
parent
6fd1a47460
commit
e1214d97b6
12 changed files with 249 additions and 233 deletions
|
@ -4,6 +4,7 @@ require 'draper/version'
|
|||
require 'draper/system'
|
||||
require 'draper/active_model_support'
|
||||
require 'draper/view_helpers'
|
||||
require 'draper/finders'
|
||||
require 'draper/decorator'
|
||||
require 'draper/helper_proxy'
|
||||
require 'draper/lazy_helpers'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
require 'active_support/core_ext/class/attribute'
|
||||
require 'active_support/core_ext/array/extract_options'
|
||||
|
||||
module Draper
|
||||
|
@ -6,7 +5,6 @@ module Draper
|
|||
include ActiveModelSupport
|
||||
include Draper::ViewHelpers
|
||||
|
||||
class_attribute :model_class
|
||||
attr_accessor :model, :options
|
||||
|
||||
# Initialize a new decorator instance by passing in
|
||||
|
@ -24,39 +22,23 @@ module Draper
|
|||
# @param [Hash] options (optional)
|
||||
def initialize(input, options = {})
|
||||
input.to_a if input.respond_to?(:to_a) # forces evaluation of a lazy query from AR
|
||||
self.class.model_class = input.class if model_class.nil?
|
||||
self.model = input
|
||||
self.options = options
|
||||
handle_multiple_decoration if input.is_a?(Draper::Decorator)
|
||||
end
|
||||
|
||||
# Proxies to the class specified by `decorates` to automatically
|
||||
# lookup an object in the database and decorate it.
|
||||
# Adds ActiveRecord finder methods to the decorator class. The
|
||||
# methods return decorated models, so that you can use
|
||||
# `ProductDecorator.find(id)` instead of
|
||||
# `ProductDecorator.decorate(Product.find(id))`.
|
||||
#
|
||||
# @param [Symbol or String] id id to lookup
|
||||
# @param [Hash] options additional options to the find method of the model
|
||||
# @return [Object] instance of this decorator class
|
||||
def self.find(id, options = {})
|
||||
self.new(model_class.find(id), options)
|
||||
end
|
||||
|
||||
# Typically called within a decorator definition, this method
|
||||
# specifies the name of the wrapped object class.
|
||||
# If the `:for` option is not supplied, the model class will be
|
||||
# inferred from the decorator class.
|
||||
#
|
||||
# For instance, a `ProductDecorator` class might call `decorates :product`
|
||||
#
|
||||
# But they don't have to match in name, so a `EmployeeDecorator`
|
||||
# class could call `decorates :person` to wrap instances of `Person`
|
||||
#
|
||||
# This is primarilly set so the `.find` method knows which class
|
||||
# to query.
|
||||
#
|
||||
# @param [Symbol] class_name snakecase name of the decorated class, like `:product`
|
||||
def self.decorates(class_name, options = {})
|
||||
self.model_class = options[:class] || options[:class_name] || class_name.to_s.camelize
|
||||
self.model_class = model_class.constantize if model_class.respond_to?(:constantize)
|
||||
model_class.send :include, Draper::Decoratable
|
||||
define_method(class_name) { @model }
|
||||
# @option options [Class, Symbol] :for The model class to find
|
||||
def self.add_finders(options = {})
|
||||
extend Draper::Finders
|
||||
self.finder_class = options[:for] || name.chomp("Decorator")
|
||||
end
|
||||
|
||||
# Typically called within a decorator definition, this method causes
|
||||
|
@ -156,22 +138,6 @@ module Draper
|
|||
end
|
||||
end
|
||||
|
||||
# Fetch all instances of the decorated class and decorate them.
|
||||
#
|
||||
# @param [Hash] options (optional)
|
||||
# @return [Draper::CollectionDecorator]
|
||||
def self.all(options = {})
|
||||
Draper::CollectionDecorator.new(model_class.all, self, options)
|
||||
end
|
||||
|
||||
def self.first(options = {})
|
||||
decorate(model_class.first, options)
|
||||
end
|
||||
|
||||
def self.last(options = {})
|
||||
decorate(model_class.last, options)
|
||||
end
|
||||
|
||||
# Get the chain of decorators applied to the object.
|
||||
#
|
||||
# @return [Array] list of decorator classes
|
||||
|
@ -238,18 +204,6 @@ module Draper
|
|||
raise no_method_error
|
||||
end
|
||||
|
||||
def self.method_missing(method, *args, &block)
|
||||
if method.to_s.match(/^find_((all_|last_)?by_|or_(initialize|create)_by_).*/)
|
||||
self.decorate(model_class.send(method, *args, &block), :context => args.dup.extract_options!)
|
||||
else
|
||||
model_class.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def self.respond_to?(method, include_private = false)
|
||||
super || model_class.respond_to?(method)
|
||||
end
|
||||
|
||||
def context
|
||||
options.fetch(:context, {})
|
||||
end
|
||||
|
|
38
lib/draper/finders.rb
Normal file
38
lib/draper/finders.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
module Draper
|
||||
module Finders
|
||||
|
||||
attr_reader :finder_class
|
||||
def finder_class=(klass)
|
||||
@finder_class = klass.to_s.camelize.constantize
|
||||
end
|
||||
|
||||
def find(id, options = {})
|
||||
decorate(finder_class.find(id), options)
|
||||
end
|
||||
|
||||
def all(options = {})
|
||||
decorate(finder_class.all, options)
|
||||
end
|
||||
|
||||
def first(options = {})
|
||||
decorate(finder_class.first, options)
|
||||
end
|
||||
|
||||
def last(options = {})
|
||||
decorate(finder_class.last, options)
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
if method.to_s.match(/^find_((all_|last_)?by_|or_(initialize|create)_by_).*/)
|
||||
decorate(finder_class.send(method, *args, &block), context: args.dup.extract_options!)
|
||||
else
|
||||
finder_class.send(method, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to?(method, include_private = false)
|
||||
super || finder_class.respond_to?(method)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -7,10 +7,6 @@ describe Draper::Decorator do
|
|||
let(:non_active_model_source){ NonActiveModelProduct.new }
|
||||
|
||||
describe "#initialize" do
|
||||
it "sets the model class for the decorator" do
|
||||
ProductDecorator.new(source).model_class.should == Product
|
||||
end
|
||||
|
||||
it "does not re-apply on instances of itself" do
|
||||
product_decorator = ProductDecorator.new(source)
|
||||
ProductDecorator.new(product_decorator).model.should be_instance_of Product
|
||||
|
@ -168,20 +164,6 @@ describe Draper::Decorator do
|
|||
end
|
||||
end
|
||||
|
||||
describe "proxying class methods" do
|
||||
it "pass missing class method calls on to the wrapped class" do
|
||||
subject.class.sample_class_method.should == "sample class method"
|
||||
end
|
||||
|
||||
it "respond_to a wrapped class method" do
|
||||
subject.class.should respond_to(:sample_class_method)
|
||||
end
|
||||
|
||||
it "still respond_to its own class methods" do
|
||||
subject.class.should respond_to(:own_class_method)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#helpers" do
|
||||
it "returns a HelperProxy" do
|
||||
subject.helpers.should be_a Draper::HelperProxy
|
||||
|
@ -221,75 +203,6 @@ describe Draper::Decorator do
|
|||
end
|
||||
end
|
||||
|
||||
context(".decorates") do
|
||||
it "handle plural-like words properly'" do
|
||||
class Business; end
|
||||
expect do
|
||||
class BusinessDecorator < Draper::Decorator
|
||||
decorates:business
|
||||
end
|
||||
BusinessDecorator.model_class.should == Business
|
||||
end.to_not raise_error
|
||||
end
|
||||
|
||||
context("accepts ActiveRecord like :class_name option too") do
|
||||
it "accepts constants for :class" do
|
||||
expect do
|
||||
class CustomDecorator < Draper::Decorator
|
||||
decorates :product, :class => Product
|
||||
end
|
||||
CustomDecorator.model_class.should == Product
|
||||
end.to_not raise_error
|
||||
end
|
||||
|
||||
it "accepts constants for :class_name" do
|
||||
expect do
|
||||
class CustomDecorator < Draper::Decorator
|
||||
decorates :product, :class_name => Product
|
||||
end
|
||||
CustomDecorator.model_class.should == Product
|
||||
end.to_not raise_error
|
||||
end
|
||||
|
||||
it "accepts strings for :class" do
|
||||
expect do
|
||||
class CustomDecorator < Draper::Decorator
|
||||
decorates :product, :class => 'Product'
|
||||
end
|
||||
CustomDecorator.model_class.should == Product
|
||||
end.to_not raise_error
|
||||
end
|
||||
|
||||
it "accepts strings for :class_name" do
|
||||
expect do
|
||||
class CustomDecorator < Draper::Decorator
|
||||
decorates :product, :class_name => 'Product'
|
||||
end
|
||||
CustomDecorator.model_class.should == Product
|
||||
end.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it "creates a named accessor for the wrapped model" do
|
||||
pd = ProductDecorator.new(source)
|
||||
pd.send(:product).should == source
|
||||
end
|
||||
|
||||
context("namespaced model supporting") do
|
||||
let(:source){ Namespace::Product.new }
|
||||
|
||||
it "sets the model class for the decorator" do
|
||||
decorator = Namespace::ProductDecorator.new(source)
|
||||
decorator.model_class.should == Namespace::Product
|
||||
end
|
||||
|
||||
it "creates a named accessor for the wrapped model" do
|
||||
pd = Namespace::ProductDecorator.new(source)
|
||||
pd.send(:product).should == source
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".decorates_association" do
|
||||
context "for ActiveModel collection associations" do
|
||||
before { subject.class.decorates_association :similar_products }
|
||||
|
@ -515,72 +428,6 @@ describe Draper::Decorator do
|
|||
subject.block{"marker"}.should == "marker"
|
||||
end
|
||||
|
||||
context ".find" do
|
||||
it "lookup the associated model when passed an integer" do
|
||||
pd = ProductDecorator.find(1)
|
||||
pd.should be_instance_of(ProductDecorator)
|
||||
pd.model.should be_instance_of(Product)
|
||||
end
|
||||
|
||||
it "lookup the associated model when passed a string" do
|
||||
pd = ProductDecorator.find("1")
|
||||
pd.should be_instance_of(ProductDecorator)
|
||||
pd.model.should be_instance_of(Product)
|
||||
end
|
||||
|
||||
it "accept and store a context" do
|
||||
pd = ProductDecorator.find(1, :context => :admin)
|
||||
pd.context.should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
context ".find_by_(x)" do
|
||||
it "runs the similarly named finder" do
|
||||
Product.should_receive(:find_by_name)
|
||||
ProductDecorator.find_by_name("apples")
|
||||
end
|
||||
|
||||
it "returns a decorated result" do
|
||||
ProductDecorator.find_by_name("apples").should be_kind_of(ProductDecorator)
|
||||
end
|
||||
|
||||
it "runs complex finders" do
|
||||
Product.should_receive(:find_by_name_and_size)
|
||||
ProductDecorator.find_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "runs find_all_by_(x) finders" do
|
||||
Product.should_receive(:find_all_by_name_and_size)
|
||||
ProductDecorator.find_all_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "runs find_last_by_(x) finders" do
|
||||
Product.should_receive(:find_last_by_name_and_size)
|
||||
ProductDecorator.find_last_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "runs find_or_initialize_by_(x) finders" do
|
||||
Product.should_receive(:find_or_initialize_by_name_and_size)
|
||||
ProductDecorator.find_or_initialize_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "runs find_or_create_by_(x) finders" do
|
||||
Product.should_receive(:find_or_create_by_name_and_size)
|
||||
ProductDecorator.find_or_create_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "accepts an options hash" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
|
||||
ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
|
||||
end
|
||||
|
||||
it "uses the options hash in the decorator instantiation" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", {:role => :admin})
|
||||
pd = ProductDecorator.find_by_name_and_size("apples", "large", {:role => :admin})
|
||||
pd.context[:role].should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
describe "#==" do
|
||||
it "compares the decorated models" do
|
||||
other = Draper::Decorator.new(source)
|
||||
|
@ -599,24 +446,6 @@ describe Draper::Decorator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'position accessors' do
|
||||
[:first, :last].each do |method|
|
||||
context "##{method}" do
|
||||
it "return a decorated instance" do
|
||||
ProductDecorator.send(method).should be_instance_of ProductDecorator
|
||||
end
|
||||
|
||||
it "return the #{method} instance of the wrapped class" do
|
||||
ProductDecorator.send(method).model.should == Product.send(method)
|
||||
end
|
||||
|
||||
it "accept an optional context" do
|
||||
ProductDecorator.send(method, :context => :admin).context.should == :admin
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "method security" do
|
||||
subject(:decorator_class) { Draper::Decorator }
|
||||
let(:security) { stub }
|
||||
|
@ -720,4 +549,44 @@ describe Draper::Decorator do
|
|||
subject.kind_of?(subject.class).should be_true
|
||||
subject.is_a?(subject.class).should be_true
|
||||
end
|
||||
|
||||
describe ".add_finders" do
|
||||
it "extends the Finders module" do
|
||||
ProductDecorator.should be_a_kind_of Draper::Finders
|
||||
end
|
||||
|
||||
context "with no options" do
|
||||
it "infers the finder class" do
|
||||
ProductDecorator.finder_class.should be Product
|
||||
end
|
||||
|
||||
context "for a namespaced model" do
|
||||
it "infers the finder class" do
|
||||
Namespace::ProductDecorator.finder_class.should be Namespace::Product
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with for: symbol" do
|
||||
it "sets the finder class" do
|
||||
FinderDecorator.add_finders for: :product
|
||||
FinderDecorator.finder_class.should be Product
|
||||
end
|
||||
end
|
||||
|
||||
context "with for: string" do
|
||||
it "sets the finder class" do
|
||||
FinderDecorator.add_finders for: "some_thing"
|
||||
FinderDecorator.finder_class.should be SomeThing
|
||||
end
|
||||
end
|
||||
|
||||
context "with for: class" do
|
||||
it "sets the finder_class" do
|
||||
FinderDecorator.add_finders for: Namespace::Product
|
||||
FinderDecorator.finder_class.should be Namespace::Product
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
151
spec/draper/finders_spec.rb
Normal file
151
spec/draper/finders_spec.rb
Normal file
|
@ -0,0 +1,151 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Draper::Finders do
|
||||
describe ".find" do
|
||||
it "proxies to the model class" do
|
||||
Product.should_receive(:find).with(1)
|
||||
ProductDecorator.find(1)
|
||||
end
|
||||
|
||||
it "decorates the result" do
|
||||
found = Product.new
|
||||
Product.stub(:find).and_return(found)
|
||||
decorator = ProductDecorator.find(1)
|
||||
decorator.should be_a ProductDecorator
|
||||
decorator.source.should be found
|
||||
end
|
||||
|
||||
it "accepts a context" do
|
||||
decorator = ProductDecorator.find(1, context: :admin)
|
||||
decorator.context.should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
describe ".find_by_(x)" do
|
||||
it "proxies to the model class" do
|
||||
Product.should_receive(:find_by_name).with("apples")
|
||||
ProductDecorator.find_by_name("apples")
|
||||
end
|
||||
|
||||
it "decorates the result" do
|
||||
found = Product.new
|
||||
Product.stub(:find_by_name).and_return(found)
|
||||
decorator = ProductDecorator.find_by_name("apples")
|
||||
decorator.should be_a ProductDecorator
|
||||
decorator.source.should be found
|
||||
end
|
||||
|
||||
it "proxies complex finders" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large")
|
||||
ProductDecorator.find_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "proxies find_all_by_(x) finders" do
|
||||
Product.should_receive(:find_all_by_name_and_size).with("apples", "large")
|
||||
ProductDecorator.find_all_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "proxies find_last_by_(x) finders" do
|
||||
Product.should_receive(:find_last_by_name_and_size).with("apples", "large")
|
||||
ProductDecorator.find_last_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "proxies find_or_initialize_by_(x) finders" do
|
||||
Product.should_receive(:find_or_initialize_by_name_and_size).with("apples", "large")
|
||||
ProductDecorator.find_or_initialize_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "proxies find_or_create_by_(x) finders" do
|
||||
Product.should_receive(:find_or_create_by_name_and_size).with("apples", "large")
|
||||
ProductDecorator.find_or_create_by_name_and_size("apples", "large")
|
||||
end
|
||||
|
||||
it "accepts options" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", {role: :admin})
|
||||
ProductDecorator.find_by_name_and_size("apples", "large", role: :admin)
|
||||
end
|
||||
|
||||
it "sets the context to the options" do
|
||||
Product.should_receive(:find_by_name_and_size).with("apples", "large", {role: :admin})
|
||||
decorator = ProductDecorator.find_by_name_and_size("apples", "large", role: :admin)
|
||||
decorator.context.should == {role: :admin}
|
||||
end
|
||||
end
|
||||
|
||||
describe ".all" do
|
||||
it "returns a decorated collection" do
|
||||
collection = ProductDecorator.all
|
||||
collection.should be_a Draper::CollectionDecorator
|
||||
collection.first.should be_a ProductDecorator
|
||||
end
|
||||
|
||||
it "accepts a context" do
|
||||
collection = ProductDecorator.all(context: :admin)
|
||||
collection.first.context.should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
describe ".first" do
|
||||
it "proxies to the model class" do
|
||||
Product.should_receive(:first)
|
||||
ProductDecorator.first
|
||||
end
|
||||
|
||||
it "decorates the result" do
|
||||
first = Product.new
|
||||
Product.stub(:first).and_return(first)
|
||||
decorator = ProductDecorator.first
|
||||
decorator.should be_a ProductDecorator
|
||||
decorator.source.should be first
|
||||
end
|
||||
|
||||
it "accepts a context" do
|
||||
decorator = ProductDecorator.first(context: :admin)
|
||||
decorator.context.should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
describe ".last" do
|
||||
it "proxies to the model class" do
|
||||
Product.should_receive(:last)
|
||||
ProductDecorator.last
|
||||
end
|
||||
|
||||
it "decorates the result" do
|
||||
last = Product.new
|
||||
Product.stub(:last).and_return(last)
|
||||
decorator = ProductDecorator.last
|
||||
decorator.should be_a ProductDecorator
|
||||
decorator.source.should be last
|
||||
end
|
||||
|
||||
it "accepts a context" do
|
||||
decorator = ProductDecorator.last(context: :admin)
|
||||
decorator.context.should == :admin
|
||||
end
|
||||
end
|
||||
|
||||
describe "scopes" do
|
||||
it "proxies to the model class" do
|
||||
Product.should_receive(:where).with({name: "apples"})
|
||||
ProductDecorator.where(name: "apples")
|
||||
end
|
||||
|
||||
it "doesn't decorate the result" do
|
||||
found = [Product.new]
|
||||
Product.stub(:where).and_return(found)
|
||||
ProductDecorator.where(name: "apples").should be found
|
||||
end
|
||||
end
|
||||
|
||||
describe ".respond_to?" do
|
||||
it "responds to the model's class methods" do
|
||||
ProductDecorator.should respond_to :sample_class_method
|
||||
end
|
||||
|
||||
it "responds to its own methods" do
|
||||
ProductDecorator.should respond_to :my_class_method
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
class PostsController < ApplicationController
|
||||
def show
|
||||
@post = PostDecorator.find(params[:id])
|
||||
@post = Post.find(params[:id]).decorate
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
class PostDecorator < Draper::Decorator
|
||||
decorates :post
|
||||
|
||||
def posted_date
|
||||
if created_at.to_date == Date.today
|
||||
"Today"
|
||||
|
|
|
@ -14,6 +14,7 @@ require './spec/support/samples/decorator_with_denies'
|
|||
require './spec/support/samples/decorator_with_denies_all'
|
||||
require './spec/support/samples/decorator_with_special_methods'
|
||||
require './spec/support/samples/enumerable_proxy'
|
||||
require './spec/support/samples/finder_decorator'
|
||||
require './spec/support/samples/namespaced_product'
|
||||
require './spec/support/samples/namespaced_product_decorator'
|
||||
require './spec/support/samples/non_active_model_product'
|
||||
|
|
2
spec/support/samples/finder_decorator.rb
Normal file
2
spec/support/samples/finder_decorator.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
class FinderDecorator < Draper::Decorator
|
||||
end
|
|
@ -2,6 +2,6 @@ require './spec/support/samples/namespaced_product'
|
|||
|
||||
module Namespace
|
||||
class ProductDecorator < Draper::Decorator
|
||||
decorates :product, :class => Namespace::Product
|
||||
add_finders
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
class ProductDecorator < Draper::Decorator
|
||||
decorates :product
|
||||
add_finders
|
||||
|
||||
def awesome_title
|
||||
"Awesome Title"
|
||||
end
|
||||
|
||||
def self.my_class_method
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
class SomeThingDecorator < Draper::Decorator
|
||||
decorates :some_thing
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue