Remove .decorates method

This commit is contained in:
Andrew Haines 2012-10-14 23:35:25 +01:00
parent 6fd1a47460
commit e1214d97b6
12 changed files with 249 additions and 233 deletions

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
class PostsController < ApplicationController
def show
@post = PostDecorator.find(params[:id])
@post = Post.find(params[:id]).decorate
end
end

View File

@ -1,6 +1,4 @@
class PostDecorator < Draper::Decorator
decorates :post
def posted_date
if created_at.to_date == Date.today
"Today"

View File

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

View File

@ -0,0 +1,2 @@
class FinderDecorator < Draper::Decorator
end

View File

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

View File

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

View File

@ -1,3 +1,2 @@
class SomeThingDecorator < Draper::Decorator
decorates :some_thing
end