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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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