Remove `allows`, `denies` and `denies_all` from Decorator

Automatic delegation of methods is now achieved with `delegate_all`,
which includes the new AutomaticDelegation module.

Manual delegation is achieved using the standard Active Support
`delegate` method, which is enhanced so that `to: :source` is the
default.
This commit is contained in:
Andrew Haines 2013-01-14 03:27:58 +00:00
parent 740b32ee5a
commit c6f8aaa2b2
15 changed files with 207 additions and 603 deletions

View File

@ -35,6 +35,8 @@ could be better written as:
```ruby ```ruby
# app/decorators/article_decorator.rb # app/decorators/article_decorator.rb
class ArticleDecorator < Draper::Decorator class ArticleDecorator < Draper::Decorator
delegate_all
def publication_status def publication_status
if published? if published?
"Published at #{published_at}" "Published at #{published_at}"
@ -49,7 +51,7 @@ class ArticleDecorator < Draper::Decorator
end end
``` ```
Notice that the `published?` method can be called even though `ArticleDecorator` doesn't define it - the decorator delegates methods to the source model. However, we can override methods like `published_at` to add presentation-specific formatting, in which case we access the underlying model using the `source` method. Notice that the `published?` method can be called even though `ArticleDecorator` doesn't define it - thanks to `delegate_all`, the decorator delegates missing methods to the source model. However, we can override methods like `published_at` to add presentation-specific formatting, in which case we access the underlying model using the `source` method.
You might have heard this sort of decorator called a "presenter", an "exhibit", a "view model", or even just a "view" (in that nomenclature, what Rails calls "views" are actually "templates"). Whatever you call it, it's a great way to replace procedural helpers like the one above with "real" object-oriented programming. You might have heard this sort of decorator called a "presenter", an "exhibit", a "view model", or even just a "view" (in that nomenclature, what Rails calls "views" are actually "templates"). Whatever you call it, it's a great way to replace procedural helpers like the one above with "real" object-oriented programming.
@ -105,6 +107,8 @@ Decorators will delegate methods to the model where possible, which means in mos
```ruby ```ruby
class ArticleDecorator < Draper::Decorator class ArticleDecorator < Draper::Decorator
delegate_all
def published_at def published_at
source.published_at.strftime("%A, %B %e") source.published_at.strftime("%A, %B %e")
end end
@ -158,14 +162,16 @@ end
Draper guesses the decorator used for each item from the name of the collection decorator (`ArticlesDecorator` becomes `ArticleDecorator`). If that fails, it falls back to using each item's `decorate` method. Alternatively, you can specify a decorator by overriding the collection decorator's `decorator_class` method. Draper guesses the decorator used for each item from the name of the collection decorator (`ArticlesDecorator` becomes `ArticleDecorator`). If that fails, it falls back to using each item's `decorate` method. Alternatively, you can specify a decorator by overriding the collection decorator's `decorator_class` method.
Some pagination gems add methods to `ActiveRecord::Relation`. For example, [Kaminari](https://github.com/amatsuda/kaminari)'s `paginate` helper method requires the collection to implement `current_page`, `total_pages`, and `limit_value`. To expose these on a collection decorator, you can simply delegate to `source`: Some pagination gems add methods to `ActiveRecord::Relation`. For example, [Kaminari](https://github.com/amatsuda/kaminari)'s `paginate` helper method requires the collection to implement `current_page`, `total_pages`, and `limit_value`. To expose these on a collection decorator, you can simply delegate to the `source`:
```ruby ```ruby
class PaginatingDecorator < Draper::CollectionDecorator class PaginatingDecorator < Draper::CollectionDecorator
delegate :current_page, :total_pages, :limit_value, to: :source delegate :current_page, :total_pages, :limit_value
end end
``` ```
The `delegate` method used here is the same as that added by [Active Support](http://api.rubyonrails.org/classes/Module.html#method-i-delegate), except that the `:to` option is not required; it defaults to `:source` when omitted.
### Handy shortcuts ### Handy shortcuts
You can automatically decorate associated models: You can automatically decorate associated models:
@ -229,25 +235,16 @@ and inherit from it instead of directly from `Draper::Decorator`.
### Enforcing an interface between controllers and views ### Enforcing an interface between controllers and views
If you want to strictly control which methods are called in your views, you can restrict the methods that the decorator delegates to the model. Use `denies` to blacklist methods: The `delegate_all` call at the top of your decorator means that all missing methods will delegated to the source. If you want to strictly control which methods are called in your views, you can choose to only delegate certain methods.
```ruby ```ruby
class ArticleDecorator < Draper::Decorator class ArticleDecorator < Draper::Decorator
# allow everything except `title` and `author` to be delegated delegate :title, :author
denies :title, :author
end end
``` ```
or, better, use `allows` for a whitelist: As mentioned above for `CollectionDecorator`, the `delegate` method defaults to using `:source` if the `:to` option is omitted.
```ruby
class ArticleDecorator < Draper::Decorator
# only allow `title` and `author` to be delegated to the model
allows :title, :author
end
```
You can prevent method delegation altogether using `denies_all`.
### Adding context ### Adding context

View File

@ -2,13 +2,14 @@ require 'action_view'
require 'draper/version' require 'draper/version'
require 'draper/view_helpers' require 'draper/view_helpers'
require 'draper/delegation'
require 'draper/automatic_delegation'
require 'draper/finders' require 'draper/finders'
require 'draper/decorator' require 'draper/decorator'
require 'draper/helper_proxy' require 'draper/helper_proxy'
require 'draper/lazy_helpers' require 'draper/lazy_helpers'
require 'draper/decoratable' require 'draper/decoratable'
require 'draper/decorated_association' require 'draper/decorated_association'
require 'draper/security'
require 'draper/helper_support' require 'draper/helper_support'
require 'draper/view_context' require 'draper/view_context'
require 'draper/collection_decorator' require 'draper/collection_decorator'

View File

@ -0,0 +1,50 @@
module Draper
module AutomaticDelegation
extend ActiveSupport::Concern
# Delegates missing instance methods to the source object.
def method_missing(method, *args, &block)
return super unless delegatable?(method)
self.class.delegate method
send(method, *args, &block)
end
# Checks if the decorator responds to an instance method, or is able to
# proxy it to the source object.
def respond_to?(method, include_private = false)
super || delegatable?(method)
end
# @private
def delegatable?(method)
source.respond_to?(method)
end
module ClassMethods
# Proxies missing class methods to the source class.
def method_missing(method, *args, &block)
return super unless delegatable?(method)
source_class.send(method, *args, &block)
end
# Checks if the decorator responds to a class method, or is able to proxy
# it to the source class.
def respond_to?(method, include_private = false)
super || delegatable?(method)
end
# @private
def delegatable?(method)
source_class? && source_class.respond_to?(method)
end
end
included do
private :delegatable?
private_class_method :delegatable?
end
end
end

View File

@ -1,7 +1,8 @@
module Draper module Draper
class CollectionDecorator class CollectionDecorator
include Enumerable include Enumerable
include ViewHelpers include Draper::ViewHelpers
extend Draper::Delegation
# @return [Hash] extra data to be used in user-defined methods, and passed # @return [Hash] extra data to be used in user-defined methods, and passed
# to each item's decorator. # to each item's decorator.

View File

@ -3,6 +3,7 @@ require 'active_support/core_ext/array/extract_options'
module Draper module Draper
class Decorator class Decorator
include Draper::ViewHelpers include Draper::ViewHelpers
extend Draper::Delegation
include ActiveModel::Serialization if defined?(ActiveModel::Serialization) include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
# @return the object being decorated. # @return the object being decorated.
@ -36,6 +37,14 @@ module Draper
alias_method :decorate, :new alias_method :decorate, :new
end end
# Automatically delegates instance methods to the source object. Class
# methods will be delegated to the {source_class}, if it is set.
#
# @return [void]
def self.delegate_all
include Draper::AutomaticDelegation
end
# Sets the source class corresponding to the decorator class. # Sets the source class corresponding to the decorator class.
# #
# @note This is only necessary if you wish to proxy class methods to the # @note This is only necessary if you wish to proxy class methods to the
@ -112,37 +121,6 @@ module Draper
end end
end end
# Specifies a blacklist of methods which are not to be automatically
# proxied to the source object.
#
# @note Use only one of {allows}, {denies}, and {denies_all}.
# @param [Symbols*] methods
# list of methods not to be automatically proxied.
# @return [void]
def self.denies(*methods)
security.denies(*methods)
end
# Prevents all methods from being automatically proxied to the source
# object.
#
# @note (see denies)
# @return [void]
def self.denies_all
security.denies_all
end
# Specifies a whitelist of methods which are to be automatically proxied to
# the source object.
#
# @note (see denies)
# @param [Symbols*] methods
# list of methods to be automatically proxied.
# @return [void]
def self.allows(*methods)
security.allows(*methods)
end
# Decorates a collection of objects. The class of the collection decorator # Decorates a collection of objects. The class of the collection decorator
# is inferred from the decorator class if possible (e.g. `ProductDecorator` # is inferred from the decorator class if possible (e.g. `ProductDecorator`
# maps to `ProductsDecorator`), but otherwise defaults to # maps to `ProductsDecorator`), but otherwise defaults to
@ -203,52 +181,20 @@ module Draper
super || source.instance_of?(klass) super || source.instance_of?(klass)
end end
# Delegated to the source object, in case it is `nil`. # In case source is nil
def present? delegate :present?
source.present?
end
# For ActiveModel compatibility. # ActiveModel compatibility
# @return [self] # @private
def to_model def to_model
self self
end end
# Delegated to the source object for ActiveModel compatibility. # ActiveModel compatibility
def to_param delegate :to_param, :to_partial_path
source.to_param
end
# Proxies missing instance methods to the source object. # ActiveModel compatibility
def method_missing(method, *args, &block) singleton_class.delegate :model_name, to: :source_class
if delegatable_method?(method)
self.class.define_proxy(method)
send(method, *args, &block)
else
super
end
end
# Checks if the decorator responds to an instance method, or is able to
# proxy it to the source object.
def respond_to?(method, include_private = false)
super || delegatable_method?(method)
end
# Proxies missing class methods to the {source_class}.
def self.method_missing(method, *args, &block)
if delegatable_method?(method)
source_class.send(method, *args, &block)
else
super
end
end
# Checks if the decorator responds to a class method, or is able to proxy
# it to the {source_class}.
def self.respond_to?(method, include_private = false)
super || delegatable_method?(method)
end
# @return [Class] the class created by {decorate_collection}. # @return [Class] the class created by {decorate_collection}.
def self.collection_decorator_class def self.collection_decorator_class
@ -259,14 +205,6 @@ module Draper
private private
def delegatable_method?(method)
allow?(method) && source.respond_to?(method)
end
def self.delegatable_method?(method)
source_class? && source_class.respond_to?(method)
end
def self.source_name def self.source_name
raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/ raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/
name.chomp("Decorator") name.chomp("Decorator")
@ -284,33 +222,6 @@ module Draper
"#{plural}Decorator" "#{plural}Decorator"
end end
def self.define_proxy(method)
define_method(method) do |*args, &block|
source.send(method, *args, &block)
end
end
def self.security
@security ||= Security.new(superclass_security)
end
def self.security?
@security || (superclass.respond_to?(:security?) && superclass.security?)
end
def self.superclass_security
return nil unless superclass.respond_to?(:security)
superclass.security
end
def allow?(method)
self.class.allow?(method)
end
def self.allow?(method)
!security? || security.allow?(method)
end
def handle_multiple_decoration(options) def handle_multiple_decoration(options)
if source.applied_decorators.last == self.class if source.applied_decorators.last == self.class
@context = source.context unless options.has_key?(:context) @context = source.context unless options.has_key?(:context)

13
lib/draper/delegation.rb Normal file
View File

@ -0,0 +1,13 @@
module Draper
module Delegation
# @overload delegate(*methods, options = {})
# Overrides {http://api.rubyonrails.org/classes/Module.html#method-i-delegate Module.delegate}
# to make `:source` the default delegation target.
#
# @return [void]
def delegate(*methods)
options = methods.extract_options!
super *methods, options.reverse_merge(to: :source)
end
end
end

View File

@ -22,16 +22,15 @@ module Draper
# Decorates dynamic finder methods (`find_all_by_` and friends). # Decorates dynamic finder methods (`find_all_by_` and friends).
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
result = super return super unless method =~ /^find_(all_|last_|or_(initialize_|create_))?by_/
result = source_class.send(method, *args, &block)
options = args.extract_options! options = args.extract_options!
case method.to_s if method =~ /^find_all/
when /^find_((last_)?by_|or_(initialize|create)_by_)/
decorate(result, options)
when /^find_all_by_/
decorate_collection(result, options) decorate_collection(result, options)
else else
result decorate(result, options)
end end
end end
end end

View File

@ -1,61 +0,0 @@
module Draper
# @private
class Security
def initialize(parent = nil)
@strategy = parent.strategy if parent
@methods = []
@parent = parent
end
def denies(*methods)
apply_strategy :denies
add_methods methods
end
def denies_all
apply_strategy :denies_all
end
def allows(*methods)
apply_strategy :allows
add_methods methods
end
def allow?(method)
case strategy
when :allows
methods.include?(method)
when :denies
!methods.include?(method)
when :denies_all
false
when nil
true
end
end
protected
attr_reader :strategy
def methods
return @methods unless parent
@methods + parent.methods
end
private
attr_reader :parent
def apply_strategy(new_strategy)
raise ArgumentError, "Use only one of 'allows', 'denies', or 'denies_all'." if strategy && strategy != new_strategy
@strategy = new_strategy
end
def add_methods(new_methods)
raise ArgumentError, "Specify at least one method when using #{strategy}" if new_methods.empty?
@methods += new_methods.map(&:to_sym)
end
end
end

View File

@ -4,34 +4,16 @@ class <%= class_name %>Decorator < <%= parent_class_name %>
<%- else -%> <%- else -%>
class <%= class_name %> class <%= class_name %>
<%- end -%> <%- end -%>
delegate_all
# Accessing Helpers # Define presentation-specific methods here. Helpers are accessed through
# You can access any helper via a proxy # `helpers` (aka `h`). You can override attributes, for example:
#
# Normal Usage: helpers.number_to_currency(2)
# Abbreviated : h.number_to_currency(2)
#
# Or, optionally enable "lazy helpers" by including this module:
# include Draper::LazyHelpers
# Then use the helpers with no proxy:
# number_to_currency(2)
# Defining an Interface
# Control access to the wrapped subject's methods using one of the following:
#
# To allow only the listed methods (whitelist):
# allows :method1, :method2
#
# To allow everything except the listed methods (blacklist):
# denies :method1, :method2
# Presentation Methods
# Define your own instance methods, even overriding accessors
# generated by ActiveRecord:
# #
# def created_at # def created_at
# h.content_tag :span, attributes["created_at"].strftime("%a %m/%d/%y"), # helpers.content_tag :span, class: 'time' do
# :class => 'timestamp' # source.created_at.strftime("%a %m/%d/%y")
# end
# end # end
end end
<% end -%> <% end -%>

View File

@ -127,6 +127,20 @@ describe Draper::CollectionDecorator do
end end
end end
describe ".delegate" do
subject { Class.new(Draper::CollectionDecorator) }
it "defaults the :to option to :source" do
Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
subject.delegate :foo, :bar
end
it "does not overwrite the :to option if supplied" do
Draper::CollectionDecorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
subject.delegate :foo, :bar, to: :baz
end
end
describe "#find" do describe "#find" do
context "with a block" do context "with a block" do
it "decorates Enumerable#find" do it "decorates Enumerable#find" do

View File

@ -439,101 +439,45 @@ describe Draper::Decorator do
end end
end end
describe "#respond_to?" do describe ".delegate" do
subject { Class.new(Draper::Decorator) }
it "defaults the :to option to :source" do
Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
subject.delegate :foo, :bar
end
it "does not overwrite the :to option if supplied" do
Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
subject.delegate :foo, :bar, to: :baz
end
end
describe ".delegate_all" do
let(:decorator_class) { Class.new(ProductDecorator) } let(:decorator_class) { Class.new(ProductDecorator) }
before { decorator_class.delegate_all }
it "returns true for its own methods" do describe "#method_missing" do
subject.should respond_to :awesome_title it "does not delegate methods that are defined on the decorator" do
end
it "returns true for the source's methods" do
subject.should respond_to :title
end
context "with include_private" do
it "returns true for its own private methods" do
subject.respond_to?(:awesome_private_title, true).should be_true
end
it "returns false for the source's private methods" do
subject.respond_to?(:private_title, true).should be_false
end
end
context "with method security" do
it "respects allows" do
subject.class.allows :hello_world
subject.should respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
it "respects denies" do
subject.class.denies :goodnight_moon
subject.should respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
it "respects denies_all" do
subject.class.denies_all
subject.should_not respond_to :hello_world
subject.should_not respond_to :goodnight_moon
end
end
end
describe ".respond_to?" do
subject { Class.new(ProductDecorator) }
context "without a source class" do
it "returns true for its own class methods" do
subject.should respond_to :my_class_method
end
it "returns false for other class methods" do
subject.should_not respond_to :sample_class_method
end
end
context "with a source_class" do
before { subject.decorates :product }
it "returns true for its own class methods" do
subject.should respond_to :my_class_method
end
it "returns true for the source's class methods" do
subject.should respond_to :sample_class_method
end
end
end
describe "proxying" do
context "instance methods" do
let(:decorator_class) { Class.new(ProductDecorator) }
it "does not proxy methods that are defined on the decorator" do
subject.overridable.should be :overridden subject.overridable.should be :overridden
end end
it "does not proxy methods inherited from Object" do it "does not delegate methods inherited from Object" do
subject.inspect.should_not be source.inspect subject.inspect.should_not be source.inspect
end end
it "proxies missing methods that exist on the source" do it "delegates missing methods that exist on the source" do
source.stub(:hello_world).and_return(:proxied) source.stub(:hello_world).and_return(:delegated)
subject.hello_world.should be :proxied subject.hello_world.should be :delegated
end end
it "adds proxied methods to the decorator when they are used" do it "adds delegated methods to the decorator when they are used" do
subject.methods.should_not include :hello_world subject.methods.should_not include :hello_world
subject.hello_world subject.hello_world
subject.methods.should include :hello_world subject.methods.should include :hello_world
end end
it "passes blocks to proxied methods" do it "passes blocks to delegated methods" do
subject.block{"marker"}.should == "marker" subject.block{"marker"}.should == "marker"
end end
@ -541,132 +485,83 @@ describe Draper::Decorator do
Array(subject).should be_a Array Array(subject).should be_a Array
end end
it "proxies delegated methods" do it "delegates already-delegated methods" do
subject.delegated_method.should == "Yay, delegation" subject.delegated_method.should == "Yay, delegation"
end end
it "does not proxy private methods" do it "does not delegate private methods" do
expect{subject.private_title}.to raise_error NoMethodError expect{subject.private_title}.to raise_error NoMethodError
end end
end
context "with method security" do context ".method_missing" do
it "respects allows" do subject { decorator_class }
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
subject.class.allows :hello_world
subject.hello_world.should be :proxied context "without a source class" do
expect{subject.goodnight_moon}.to raise_error NameError it "raises a NoMethodError on missing methods" do
expect{subject.hello_world}.to raise_error NoMethodError
end
end
context "with a source class" do
let(:source_class) { Product }
before { subject.decorates source_class }
it "does not delegate methods that are defined on the decorator" do
subject.overridable.should be :overridden
end end
it "respects denies" do it "delegates missing methods that exist on the source" do
source.stub(:hello_world, :goodnight_moon).and_return(:proxied) source_class.stub(:hello_world).and_return(:delegated)
subject.class.denies :goodnight_moon subject.hello_world.should be :delegated
subject.hello_world.should be :proxied
expect{subject.goodnight_moon}.to raise_error NameError
end
it "respects denies_all" do
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
subject.class.denies_all
expect{subject.hello_world}.to raise_error NameError
expect{subject.goodnight_moon}.to raise_error NameError
end end
end end
end end
context "class methods" do describe "#respond_to?" do
subject { Class.new(ProductDecorator) } it "returns true for its own methods" do
let(:source_class) { Product } subject.should respond_to :awesome_title
before { subject.decorates source_class }
it "does not proxy methods that are defined on the decorator" do
subject.overridable.should be :overridden
end end
it "proxies missing methods that exist on the source" do it "returns true for the source's methods" do
source_class.stub(:hello_world).and_return(:proxied) subject.should respond_to :title
subject.hello_world.should be :proxied end
context "with include_private" do
it "returns true for its own private methods" do
subject.respond_to?(:awesome_private_title, true).should be_true
end
it "returns false for the source's private methods" do
subject.respond_to?(:private_title, true).should be_false
end
end end
end end
end
describe "method security" do describe ".respond_to?" do
let(:decorator_class) { Draper::Decorator } subject { decorator_class }
let(:security) { stub }
before { decorator_class.stub(:security).and_return(security) }
it "delegates .denies to Draper::Security" do context "without a source class" do
security.should_receive(:denies).with(:foo, :bar) it "returns true for its own class methods" do
decorator_class.denies :foo, :bar subject.should respond_to :my_class_method
end end
it "delegates .denies_all to Draper::Security" do it "returns false for other class methods" do
security.should_receive(:denies_all) subject.should_not respond_to :sample_class_method
decorator_class.denies_all end
end end
it "delegates .allows to Draper::Security" do context "with a source_class" do
security.should_receive(:allows).with(:foo, :bar) before { subject.decorates :product }
decorator_class.allows :foo, :bar
end
end
describe "security inheritance" do it "returns true for its own class methods" do
let(:superclass_instance) { superclass.new(source) } subject.should respond_to :my_class_method
let(:subclass_instance) { subclass.new(source) } end
let(:source) { stub(allowed: 1, denied: 2) }
let(:superclass) { Class.new(Draper::Decorator) }
let(:subclass) { Class.new(superclass) }
it "inherits allows from superclass to subclass" do it "returns true for the source's class methods" do
superclass.allows(:allowed) subject.should respond_to :sample_class_method
subclass_instance.should_not respond_to :denied end
end end
it "inherits denies from superclass to subclass" do
superclass.denies(:denied)
subclass_instance.should_not respond_to :denied
end
it "inherits denies_all from superclass to subclass" do
superclass.denies_all
subclass_instance.should_not respond_to :denied
end
it "can add extra allows methods" do
superclass.allows(:allowed)
subclass.allows(:denied)
superclass_instance.should_not respond_to :denied
subclass_instance.should respond_to :denied
end
it "can add extra denies methods" do
superclass.denies(:denied)
subclass.denies(:allowed)
superclass_instance.should respond_to :allowed
subclass_instance.should_not respond_to :allowed
end
it "does not pass allows from subclass to superclass" do
subclass.allows(:allowed)
superclass_instance.should respond_to :denied
end
it "does not pass denies from subclass to superclass" do
subclass.denies(:denied)
superclass_instance.should respond_to :denied
end
it "does not pass denies_all from subclass to superclass" do
subclass.denies_all
superclass_instance.should respond_to :denied
end
it "inherits security strategy" do
superclass.allows :allowed
expect{subclass.denies :denied}.to raise_error ArgumentError
end end
end end
@ -726,28 +621,4 @@ describe Draper::Decorator do
end end
end end
describe ".method_missing" do
context "when called on an anonymous decorator" do
subject { ->{ Class.new(Draper::Decorator).fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an uninferrable decorator" do
subject { ->{ SpecificProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
context "when called on an inferrable decorator" do
context "for a method known to the inferred class" do
subject { ->{ ProductDecorator.model_name } }
it { should_not raise_error }
end
context "for a method unknown to the inferred class" do
subject { ->{ ProductDecorator.fizzbuzz } }
it { should raise_error NoMethodError }
end
end
end
end end

View File

@ -129,17 +129,4 @@ describe Draper::Finders do
decorator.context.should == {some: 'context'} decorator.context.should == {some: 'context'}
end end
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
end end

View File

@ -1,158 +0,0 @@
require 'spec_helper'
RSpec::Matchers.define :allow do |method|
match do |subject|
subject.allow?(method)
end
end
describe Draper::Security do
subject(:security) { Draper::Security.new }
context "when newly initialized" do
it "allows any method" do
security.should allow :foo
end
end
describe "#denies" do
it "raises an error when there are no arguments" do
expect{security.denies}.to raise_error ArgumentError
end
end
context "when using denies" do
before { security.denies :foo, :bar }
it "denies the listed methods" do
security.should_not allow :foo
security.should_not allow :bar
end
it "allows other methods" do
security.should allow :baz
end
it "accepts multiple denies" do
expect{security.denies :baz}.not_to raise_error
end
it "does not accept denies_all" do
expect{security.denies_all}.to raise_error ArgumentError
end
it "does not accept allows" do
expect{security.allows :baz}.to raise_error ArgumentError
end
context "when using mulitple denies" do
before { security.denies :baz }
it "still denies the original methods" do
security.should_not allow :foo
security.should_not allow :bar
end
it "denies the additional methods" do
security.should_not allow :baz
end
it "allows other methods" do
security.should allow :qux
end
end
context "with strings" do
before { security.denies "baz" }
it "denies the method" do
security.should_not allow :baz
end
end
end
context "when using denies_all" do
before { security.denies_all }
it "denies all methods" do
security.should_not allow :foo
end
it "accepts multiple denies_all" do
expect{security.denies_all}.not_to raise_error
end
it "does not accept denies" do
expect{security.denies :baz}.to raise_error ArgumentError
end
it "does not accept allows" do
expect{security.allows :baz}.to raise_error ArgumentError
end
context "when using mulitple denies_all" do
before { security.denies_all }
it "still denies all methods" do
security.should_not allow :foo
end
end
end
describe "#allows" do
it "raises an error when there are no arguments" do
expect{security.allows}.to raise_error ArgumentError
end
end
context "when using allows" do
before { security.allows :foo, :bar }
it "allows the listed methods" do
security.should allow :foo
security.should allow :bar
end
it "denies other methods" do
security.should_not allow :baz
end
it "accepts multiple allows" do
expect{security.allows :baz}.not_to raise_error
end
it "does not accept denies" do
expect{security.denies :baz}.to raise_error ArgumentError
end
it "does not accept denies_all" do
expect{security.denies_all}.to raise_error ArgumentError
end
context "when using mulitple allows" do
before { security.allows :baz }
it "still allows the original methods" do
security.should allow :foo
security.should allow :bar
end
it "allows the additional methods" do
security.should allow :baz
end
it "denies other methods" do
security.should_not allow :qux
end
end
context "with strings" do
before { security.allows "baz" }
it "allows the method" do
security.should allow :baz
end
end
end
end

View File

@ -1,17 +1,11 @@
class PostsController < ApplicationController class PostsController < ApplicationController
def show def show
fetch_post @post = Post.find(params[:id]).decorate
end end
def mail def mail
fetch_post post = Post.find(params[:id])
email = PostMailer.decorated_email(@post).deliver email = PostMailer.decorated_email(post).deliver
render text: email.body render text: email.body
end end
private
def fetch_post
@post = Post.find(params[:id]).decorate
end
end end

View File

@ -1,6 +1,9 @@
class PostDecorator < Draper::Decorator class PostDecorator < Draper::Decorator
# need to delegate id and new_record? for AR::Base#== (Rails 3.0 only)
delegate :id, :new_record?
def posted_date def posted_date
if created_at.to_date == DateTime.now.utc.to_date if source.created_at.to_date == DateTime.now.utc.to_date
"Today" "Today"
else else
"Not Today" "Not Today"