mirror of
https://github.com/drapergem/draper
synced 2023-03-27 23:21:17 -04:00
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:
parent
740b32ee5a
commit
c6f8aaa2b2
15 changed files with 207 additions and 603 deletions
27
README.md
27
README.md
|
@ -35,6 +35,8 @@ could be better written as:
|
|||
```ruby
|
||||
# app/decorators/article_decorator.rb
|
||||
class ArticleDecorator < Draper::Decorator
|
||||
delegate_all
|
||||
|
||||
def publication_status
|
||||
if published?
|
||||
"Published at #{published_at}"
|
||||
|
@ -49,7 +51,7 @@ class ArticleDecorator < Draper::Decorator
|
|||
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.
|
||||
|
||||
|
@ -105,6 +107,8 @@ Decorators will delegate methods to the model where possible, which means in mos
|
|||
|
||||
```ruby
|
||||
class ArticleDecorator < Draper::Decorator
|
||||
delegate_all
|
||||
|
||||
def published_at
|
||||
source.published_at.strftime("%A, %B %e")
|
||||
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.
|
||||
|
||||
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
|
||||
class PaginatingDecorator < Draper::CollectionDecorator
|
||||
delegate :current_page, :total_pages, :limit_value, to: :source
|
||||
delegate :current_page, :total_pages, :limit_value
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
class ArticleDecorator < Draper::Decorator
|
||||
# allow everything except `title` and `author` to be delegated
|
||||
denies :title, :author
|
||||
delegate :title, :author
|
||||
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
|
||||
|
||||
|
|
|
@ -2,13 +2,14 @@ require 'action_view'
|
|||
|
||||
require 'draper/version'
|
||||
require 'draper/view_helpers'
|
||||
require 'draper/delegation'
|
||||
require 'draper/automatic_delegation'
|
||||
require 'draper/finders'
|
||||
require 'draper/decorator'
|
||||
require 'draper/helper_proxy'
|
||||
require 'draper/lazy_helpers'
|
||||
require 'draper/decoratable'
|
||||
require 'draper/decorated_association'
|
||||
require 'draper/security'
|
||||
require 'draper/helper_support'
|
||||
require 'draper/view_context'
|
||||
require 'draper/collection_decorator'
|
||||
|
|
50
lib/draper/automatic_delegation.rb
Normal file
50
lib/draper/automatic_delegation.rb
Normal 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
|
|
@ -1,7 +1,8 @@
|
|||
module Draper
|
||||
class CollectionDecorator
|
||||
include Enumerable
|
||||
include ViewHelpers
|
||||
include Draper::ViewHelpers
|
||||
extend Draper::Delegation
|
||||
|
||||
# @return [Hash] extra data to be used in user-defined methods, and passed
|
||||
# to each item's decorator.
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'active_support/core_ext/array/extract_options'
|
|||
module Draper
|
||||
class Decorator
|
||||
include Draper::ViewHelpers
|
||||
extend Draper::Delegation
|
||||
include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
|
||||
|
||||
# @return the object being decorated.
|
||||
|
@ -36,6 +37,14 @@ module Draper
|
|||
alias_method :decorate, :new
|
||||
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.
|
||||
#
|
||||
# @note This is only necessary if you wish to proxy class methods to the
|
||||
|
@ -112,37 +121,6 @@ module Draper
|
|||
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
|
||||
# is inferred from the decorator class if possible (e.g. `ProductDecorator`
|
||||
# maps to `ProductsDecorator`), but otherwise defaults to
|
||||
|
@ -203,52 +181,20 @@ module Draper
|
|||
super || source.instance_of?(klass)
|
||||
end
|
||||
|
||||
# Delegated to the source object, in case it is `nil`.
|
||||
def present?
|
||||
source.present?
|
||||
end
|
||||
# In case source is nil
|
||||
delegate :present?
|
||||
|
||||
# For ActiveModel compatibility.
|
||||
# @return [self]
|
||||
# ActiveModel compatibility
|
||||
# @private
|
||||
def to_model
|
||||
self
|
||||
end
|
||||
|
||||
# Delegated to the source object for ActiveModel compatibility.
|
||||
def to_param
|
||||
source.to_param
|
||||
end
|
||||
# ActiveModel compatibility
|
||||
delegate :to_param, :to_partial_path
|
||||
|
||||
# Proxies missing instance methods to the source object.
|
||||
def method_missing(method, *args, &block)
|
||||
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
|
||||
# ActiveModel compatibility
|
||||
singleton_class.delegate :model_name, to: :source_class
|
||||
|
||||
# @return [Class] the class created by {decorate_collection}.
|
||||
def self.collection_decorator_class
|
||||
|
@ -259,14 +205,6 @@ module Draper
|
|||
|
||||
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
|
||||
raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/
|
||||
name.chomp("Decorator")
|
||||
|
@ -284,33 +222,6 @@ module Draper
|
|||
"#{plural}Decorator"
|
||||
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)
|
||||
if source.applied_decorators.last == self.class
|
||||
@context = source.context unless options.has_key?(:context)
|
||||
|
|
13
lib/draper/delegation.rb
Normal file
13
lib/draper/delegation.rb
Normal 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
|
|
@ -22,16 +22,15 @@ module Draper
|
|||
|
||||
# Decorates dynamic finder methods (`find_all_by_` and friends).
|
||||
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!
|
||||
|
||||
case method.to_s
|
||||
when /^find_((last_)?by_|or_(initialize|create)_by_)/
|
||||
decorate(result, options)
|
||||
when /^find_all_by_/
|
||||
if method =~ /^find_all/
|
||||
decorate_collection(result, options)
|
||||
else
|
||||
result
|
||||
decorate(result, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -4,34 +4,16 @@ class <%= class_name %>Decorator < <%= parent_class_name %>
|
|||
<%- else -%>
|
||||
class <%= class_name %>
|
||||
<%- end -%>
|
||||
delegate_all
|
||||
|
||||
# Accessing Helpers
|
||||
# You can access any helper via a proxy
|
||||
#
|
||||
# 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:
|
||||
# Define presentation-specific methods here. Helpers are accessed through
|
||||
# `helpers` (aka `h`). You can override attributes, for example:
|
||||
#
|
||||
# def created_at
|
||||
# h.content_tag :span, attributes["created_at"].strftime("%a %m/%d/%y"),
|
||||
# :class => 'timestamp'
|
||||
# helpers.content_tag :span, class: 'time' do
|
||||
# source.created_at.strftime("%a %m/%d/%y")
|
||||
# end
|
||||
# end
|
||||
|
||||
end
|
||||
<% end -%>
|
||||
|
|
|
@ -127,6 +127,20 @@ describe Draper::CollectionDecorator do
|
|||
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
|
||||
context "with a block" do
|
||||
it "decorates Enumerable#find" do
|
||||
|
|
|
@ -439,101 +439,45 @@ describe Draper::Decorator do
|
|||
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) }
|
||||
before { decorator_class.delegate_all }
|
||||
|
||||
it "returns true for its own methods" do
|
||||
subject.should respond_to :awesome_title
|
||||
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
|
||||
describe "#method_missing" do
|
||||
it "does not delegate methods that are defined on the decorator" do
|
||||
subject.overridable.should be :overridden
|
||||
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
|
||||
end
|
||||
|
||||
it "proxies missing methods that exist on the source" do
|
||||
source.stub(:hello_world).and_return(:proxied)
|
||||
subject.hello_world.should be :proxied
|
||||
it "delegates missing methods that exist on the source" do
|
||||
source.stub(:hello_world).and_return(:delegated)
|
||||
subject.hello_world.should be :delegated
|
||||
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.hello_world
|
||||
subject.methods.should include :hello_world
|
||||
end
|
||||
|
||||
it "passes blocks to proxied methods" do
|
||||
it "passes blocks to delegated methods" do
|
||||
subject.block{"marker"}.should == "marker"
|
||||
end
|
||||
|
||||
|
@ -541,132 +485,83 @@ describe Draper::Decorator do
|
|||
Array(subject).should be_a Array
|
||||
end
|
||||
|
||||
it "proxies delegated methods" do
|
||||
it "delegates already-delegated methods" do
|
||||
subject.delegated_method.should == "Yay, delegation"
|
||||
end
|
||||
|
||||
it "does not proxy private methods" do
|
||||
it "does not delegate private methods" do
|
||||
expect{subject.private_title}.to raise_error NoMethodError
|
||||
end
|
||||
end
|
||||
|
||||
context "with method security" do
|
||||
it "respects allows" do
|
||||
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
|
||||
subject.class.allows :hello_world
|
||||
context ".method_missing" do
|
||||
subject { decorator_class }
|
||||
|
||||
subject.hello_world.should be :proxied
|
||||
expect{subject.goodnight_moon}.to raise_error NameError
|
||||
context "without a source class" do
|
||||
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
|
||||
|
||||
it "respects denies" do
|
||||
source.stub(:hello_world, :goodnight_moon).and_return(:proxied)
|
||||
subject.class.denies :goodnight_moon
|
||||
|
||||
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
|
||||
it "delegates missing methods that exist on the source" do
|
||||
source_class.stub(:hello_world).and_return(:delegated)
|
||||
subject.hello_world.should be :delegated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "class methods" do
|
||||
subject { Class.new(ProductDecorator) }
|
||||
let(:source_class) { Product }
|
||||
before { subject.decorates source_class }
|
||||
|
||||
it "does not proxy methods that are defined on the decorator" do
|
||||
subject.overridable.should be :overridden
|
||||
describe "#respond_to?" do
|
||||
it "returns true for its own methods" do
|
||||
subject.should respond_to :awesome_title
|
||||
end
|
||||
|
||||
it "proxies missing methods that exist on the source" do
|
||||
source_class.stub(:hello_world).and_return(:proxied)
|
||||
subject.hello_world.should be :proxied
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
describe "method security" do
|
||||
let(:decorator_class) { Draper::Decorator }
|
||||
let(:security) { stub }
|
||||
before { decorator_class.stub(:security).and_return(security) }
|
||||
describe ".respond_to?" do
|
||||
subject { decorator_class }
|
||||
|
||||
it "delegates .denies to Draper::Security" do
|
||||
security.should_receive(:denies).with(:foo, :bar)
|
||||
decorator_class.denies :foo, :bar
|
||||
end
|
||||
context "without a source class" do
|
||||
it "returns true for its own class methods" do
|
||||
subject.should respond_to :my_class_method
|
||||
end
|
||||
|
||||
it "delegates .denies_all to Draper::Security" do
|
||||
security.should_receive(:denies_all)
|
||||
decorator_class.denies_all
|
||||
end
|
||||
it "returns false for other class methods" do
|
||||
subject.should_not respond_to :sample_class_method
|
||||
end
|
||||
end
|
||||
|
||||
it "delegates .allows to Draper::Security" do
|
||||
security.should_receive(:allows).with(:foo, :bar)
|
||||
decorator_class.allows :foo, :bar
|
||||
end
|
||||
end
|
||||
context "with a source_class" do
|
||||
before { subject.decorates :product }
|
||||
|
||||
describe "security inheritance" do
|
||||
let(:superclass_instance) { superclass.new(source) }
|
||||
let(:subclass_instance) { subclass.new(source) }
|
||||
let(:source) { stub(allowed: 1, denied: 2) }
|
||||
let(:superclass) { Class.new(Draper::Decorator) }
|
||||
let(:subclass) { Class.new(superclass) }
|
||||
it "returns true for its own class methods" do
|
||||
subject.should respond_to :my_class_method
|
||||
end
|
||||
|
||||
it "inherits allows from superclass to subclass" do
|
||||
superclass.allows(:allowed)
|
||||
subclass_instance.should_not respond_to :denied
|
||||
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
|
||||
it "returns true for the source's class methods" do
|
||||
subject.should respond_to :sample_class_method
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -726,28 +621,4 @@ describe Draper::Decorator do
|
|||
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
|
||||
|
|
|
@ -129,17 +129,4 @@ describe Draper::Finders do
|
|||
decorator.context.should == {some: 'context'}
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -1,17 +1,11 @@
|
|||
class PostsController < ApplicationController
|
||||
def show
|
||||
fetch_post
|
||||
@post = Post.find(params[:id]).decorate
|
||||
end
|
||||
|
||||
def mail
|
||||
fetch_post
|
||||
email = PostMailer.decorated_email(@post).deliver
|
||||
post = Post.find(params[:id])
|
||||
email = PostMailer.decorated_email(post).deliver
|
||||
render text: email.body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_post
|
||||
@post = Post.find(params[:id]).decorate
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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
|
||||
if created_at.to_date == DateTime.now.utc.to_date
|
||||
if source.created_at.to_date == DateTime.now.utc.to_date
|
||||
"Today"
|
||||
else
|
||||
"Not Today"
|
||||
|
|
Loading…
Reference in a new issue