mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Filters overhaul including meantime filter support for around filters. Closes #5949.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@5163 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
79821e8bb4
commit
a0c677c8e6
4 changed files with 679 additions and 233 deletions
|
@ -1,5 +1,7 @@
|
||||||
*SVN*
|
*SVN*
|
||||||
|
|
||||||
|
* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
|
||||||
|
|
||||||
* Update RJS render tests. [sam]
|
* Update RJS render tests. [sam]
|
||||||
|
|
||||||
* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
|
* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
|
||||||
|
|
|
@ -5,19 +5,14 @@ module ActionController #:nodoc:
|
||||||
base.send(:include, ActionController::Filters::InstanceMethods)
|
base.send(:include, ActionController::Filters::InstanceMethods)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
# Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
|
||||||
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
|
||||||
# compression after the action has been performed.
|
# compression after the action has been performed. Filters have access to the request, response, and all the instance
|
||||||
#
|
# variables set by other filters in the chain or by the action (in the case of after filters).
|
||||||
# Filters have access to the request, response, and all the instance variables set by other filters in the chain
|
|
||||||
# or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
|
|
||||||
# to halt the processing before the intended action is processed by returning false or performing a redirect or render.
|
|
||||||
# This is especially useful for filters like authentication where you're not interested in allowing the action to be
|
|
||||||
# performed if the proper credentials are not in order.
|
|
||||||
#
|
#
|
||||||
# == Filter inheritance
|
# == Filter inheritance
|
||||||
#
|
#
|
||||||
# Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
|
# Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
|
||||||
# affecting the superclass. For example:
|
# affecting the superclass. For example:
|
||||||
#
|
#
|
||||||
# class BankController < ActionController::Base
|
# class BankController < ActionController::Base
|
||||||
|
@ -39,7 +34,7 @@ module ActionController #:nodoc:
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Now any actions performed on the BankController will have the audit method called before. On the VaultController,
|
# Now any actions performed on the BankController will have the audit method called before. On the VaultController,
|
||||||
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
# first the audit method is called, then the verify_credentials method. If the audit method returns false, then
|
||||||
# verify_credentials and the intended action are never called.
|
# verify_credentials and the intended action are never called.
|
||||||
#
|
#
|
||||||
# == Filter types
|
# == Filter types
|
||||||
|
@ -64,7 +59,7 @@ module ActionController #:nodoc:
|
||||||
# The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
|
# The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
|
||||||
# manipulate them as it sees fit.
|
# manipulate them as it sees fit.
|
||||||
#
|
#
|
||||||
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
# The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
|
||||||
# Or just as a quick test. It works like this:
|
# Or just as a quick test. It works like this:
|
||||||
#
|
#
|
||||||
# class WeblogController < ActionController::Base
|
# class WeblogController < ActionController::Base
|
||||||
|
@ -76,6 +71,9 @@ module ActionController #:nodoc:
|
||||||
# session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
|
# session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
|
||||||
# and returns 1 or -1 on arity will do (such as a Proc or an Method object).
|
# and returns 1 or -1 on arity will do (such as a Proc or an Method object).
|
||||||
#
|
#
|
||||||
|
# Please note that around_filters function a little differently than the normal before and after filters with regard to filter
|
||||||
|
# types. Please see the section dedicated to around_filters below.
|
||||||
|
#
|
||||||
# == Filter chain ordering
|
# == Filter chain ordering
|
||||||
#
|
#
|
||||||
# Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
|
# Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
|
||||||
|
@ -90,7 +88,7 @@ module ActionController #:nodoc:
|
||||||
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
# prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
|
||||||
#
|
#
|
||||||
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
# The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
|
||||||
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
# <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
|
||||||
# is open or not.
|
# is open or not.
|
||||||
#
|
#
|
||||||
# You may pass multiple filter arguments of each type as well as a filter block.
|
# You may pass multiple filter arguments of each type as well as a filter block.
|
||||||
|
@ -98,253 +96,507 @@ module ActionController #:nodoc:
|
||||||
#
|
#
|
||||||
# == Around filters
|
# == Around filters
|
||||||
#
|
#
|
||||||
# In addition to the individual before and after filters, it's also possible to specify that a single object should handle
|
# Around filters wrap an action, executing code both before and after.
|
||||||
# both the before and after call. That's especially useful when you need to keep state active between the before and after,
|
# They may be declared as method references, blocks, or objects responding
|
||||||
# such as the example of a benchmark filter below:
|
# to #filter or to both #before and #after.
|
||||||
#
|
#
|
||||||
# class WeblogController < ActionController::Base
|
# To use a method as an around_filter, pass a symbol naming the Ruby method.
|
||||||
# around_filter BenchmarkingFilter.new
|
# Yield (or block.call) within the method to run the action.
|
||||||
#
|
#
|
||||||
# # Before this action is performed, BenchmarkingFilter#before(controller) is executed
|
# around_filter :catch_exceptions
|
||||||
# def index
|
#
|
||||||
|
# private
|
||||||
|
# def catch_exceptions
|
||||||
|
# yield
|
||||||
|
# rescue => exception
|
||||||
|
# logger.debug "Caught exception! #{exception}"
|
||||||
|
# raise
|
||||||
# end
|
# end
|
||||||
# # After this action has been performed, BenchmarkingFilter#after(controller) is executed
|
#
|
||||||
|
# To use a block as an around_filter, pass a block taking as args both
|
||||||
|
# the controller and the action block. You can't call yield directly from
|
||||||
|
# an around_filter block; explicitly call the action block instead:
|
||||||
|
#
|
||||||
|
# around_filter do |controller, action|
|
||||||
|
# logger.debug "before #{controller.action_name}"
|
||||||
|
# action.call
|
||||||
|
# logger.debug "after #{controller.action_name}"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# To use a filter object with around_filter, pass an object responding
|
||||||
|
# to :filter or both :before and :after. With a filter method, yield to
|
||||||
|
# the block as above:
|
||||||
|
#
|
||||||
|
# around_filter BenchmarkingFilter
|
||||||
|
#
|
||||||
# class BenchmarkingFilter
|
# class BenchmarkingFilter
|
||||||
# def initialize
|
# def self.filter(controller, &block)
|
||||||
# @runtime
|
# Benchmark.measure(&block)
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def before
|
|
||||||
# start_timer
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def after
|
|
||||||
# stop_timer
|
|
||||||
# report_result
|
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# With before and after methods:
|
||||||
|
#
|
||||||
|
# around_filter Authorizer.new
|
||||||
|
#
|
||||||
|
# class Authorizer
|
||||||
|
# # This will run before the action. Returning false aborts the action.
|
||||||
|
# def before(controller)
|
||||||
|
# if user.authorized?
|
||||||
|
# return true
|
||||||
|
# else
|
||||||
|
# redirect_to login_url
|
||||||
|
# return false
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # This will run after the action if and only if before returned true.
|
||||||
|
# def after(controller)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# If the filter has before and after methods, the before method will be
|
||||||
|
# called before the action. If before returns false, the filter chain is
|
||||||
|
# halted and after will not be run. See Filter Chain Halting below for
|
||||||
|
# an example.
|
||||||
|
#
|
||||||
# == Filter chain skipping
|
# == Filter chain skipping
|
||||||
#
|
#
|
||||||
# Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
|
# Declaring a filter on a base class conveniently applies to its subclasses,
|
||||||
# subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
|
# but sometimes a subclass should skip some of its superclass' filters:
|
||||||
# they would like to be relieved of. Examples
|
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# before_filter :authenticate
|
# before_filter :authenticate
|
||||||
|
# around_filter :catch_exceptions
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class WeblogController < ApplicationController
|
# class WeblogController < ApplicationController
|
||||||
# # will run the :authenticate filter
|
# # Will run the :authenticate and :catch_exceptions filters.
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class SignupController < ApplicationController
|
# class SignupController < ApplicationController
|
||||||
# # will not run the :authenticate filter
|
# # Skip :authenticate, run :catch_exceptions.
|
||||||
# skip_before_filter :authenticate
|
# skip_before_filter :authenticate
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# class ProjectsController < ApplicationController
|
||||||
|
# # Skip :catch_exceptions, run :authenticate.
|
||||||
|
# skip_filter :catch_exceptions
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class ClientsController < ApplicationController
|
||||||
|
# # Skip :catch_exceptions and :authenticate unless action is index.
|
||||||
|
# skip_filter :catch_exceptions, :authenticate, :except => :index
|
||||||
|
# end
|
||||||
|
#
|
||||||
# == Filter conditions
|
# == Filter conditions
|
||||||
#
|
#
|
||||||
# Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
|
# Filters may be limited to specific actions by declaring the actions to
|
||||||
# exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
|
# include or exclude. Both options accept single actions (:only => :index)
|
||||||
# of which accept an arbitrary number of method references. For example:
|
# or arrays of actions (:except => [:foo, :bar]).
|
||||||
#
|
#
|
||||||
# class Journal < ActionController::Base
|
# class Journal < ActionController::Base
|
||||||
# # only require authentication if the current action is edit or delete
|
# # Require authentication for edit and delete.
|
||||||
# before_filter :authorize, :only => [ :edit, :delete ]
|
# before_filter :authorize, :only => [:edit, :delete]
|
||||||
#
|
#
|
||||||
|
# # Passing options to a filter with a block.
|
||||||
|
# around_filter(:except => :index) do |controller, action_block|
|
||||||
|
# results = Profiler.run(&action_block)
|
||||||
|
# controller.response.sub! "</body>", "#{results}</body>"
|
||||||
|
# end
|
||||||
|
#
|
||||||
# private
|
# private
|
||||||
# def authorize
|
# def authorize
|
||||||
# # redirect to login unless authenticated
|
# # Redirect to login unless authenticated.
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
|
||||||
# When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
|
|
||||||
#
|
#
|
||||||
# class UserPreferences < ActionController::Base
|
# == Filter Chain Halting
|
||||||
# before_filter(:except => :new) { # some proc ... }
|
|
||||||
# # ...
|
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
|
# <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
|
||||||
|
# before controller action is run. This is useful, for example, to deny
|
||||||
|
# access to unauthenticated users or to redirect from http to https.
|
||||||
|
# Simply return false from the filter or call render or redirect.
|
||||||
|
#
|
||||||
|
# Around filters halt the request unless the action block is called.
|
||||||
|
# Given these filters
|
||||||
|
# after_filter :after
|
||||||
|
# around_filter :around
|
||||||
|
# before_filter :before
|
||||||
|
#
|
||||||
|
# The filter chain will look like:
|
||||||
|
#
|
||||||
|
# ...
|
||||||
|
# . \
|
||||||
|
# . #around (code before yield)
|
||||||
|
# . . \
|
||||||
|
# . . #before (actual filter code is run)
|
||||||
|
# . . . \
|
||||||
|
# . . . execute controller action
|
||||||
|
# . . . /
|
||||||
|
# . . ...
|
||||||
|
# . . /
|
||||||
|
# . #around (code after yield)
|
||||||
|
# . /
|
||||||
|
# #after (actual filter code is run)
|
||||||
|
#
|
||||||
|
# If #around returns before yielding, only #after will be run. The #before
|
||||||
|
# filter and controller action will not be run. If #before returns false,
|
||||||
|
# the second half of #around and all of #after will still run but the
|
||||||
|
# action will not.
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
|
# The passed <tt>filters</tt> will be appended to the filter_chain and
|
||||||
# on this controller are performed.
|
# will execute before the action on this controller is performed.
|
||||||
def append_before_filter(*filters, &block)
|
def append_before_filter(*filters, &block)
|
||||||
conditions = extract_conditions!(filters)
|
append_filter_to_chain(filters, :before, &block)
|
||||||
filters << block if block_given?
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
append_filter_to_chain('before', filters)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
|
# The passed <tt>filters</tt> will be prepended to the filter_chain and
|
||||||
# on this controller are performed.
|
# will execute before the action on this controller is performed.
|
||||||
def prepend_before_filter(*filters, &block)
|
def prepend_before_filter(*filters, &block)
|
||||||
conditions = extract_conditions!(filters)
|
prepend_filter_to_chain(filters, :before, &block)
|
||||||
filters << block if block_given?
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
prepend_filter_to_chain('before', filters)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Short-hand for append_before_filter since that's the most common of the two.
|
# Shorthand for append_before_filter since it's the most common.
|
||||||
alias :before_filter :append_before_filter
|
alias :before_filter :append_before_filter
|
||||||
|
|
||||||
# The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
|
# The passed <tt>filters</tt> will be appended to the array of filters
|
||||||
# on this controller are performed.
|
# that run _after_ actions on this controller are performed.
|
||||||
def append_after_filter(*filters, &block)
|
def append_after_filter(*filters, &block)
|
||||||
conditions = extract_conditions!(filters)
|
prepend_filter_to_chain(filters, :after, &block)
|
||||||
filters << block if block_given?
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
append_filter_to_chain('after', filters)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
|
# The passed <tt>filters</tt> will be prepended to the array of filters
|
||||||
# on this controller are performed.
|
# that run _after_ actions on this controller are performed.
|
||||||
def prepend_after_filter(*filters, &block)
|
def prepend_after_filter(*filters, &block)
|
||||||
conditions = extract_conditions!(filters)
|
append_filter_to_chain(filters, :after, &block)
|
||||||
filters << block if block_given?
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
prepend_filter_to_chain("after", filters)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Short-hand for append_after_filter since that's the most common of the two.
|
# Shorthand for append_after_filter since it's the most common.
|
||||||
alias :after_filter :append_after_filter
|
alias :after_filter :append_after_filter
|
||||||
|
|
||||||
# The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
|
|
||||||
# on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
|
# If you append_around_filter A.new, B.new, the filter chain looks like
|
||||||
# respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
|
|
||||||
#
|
#
|
||||||
# B#before
|
# B#before
|
||||||
# A#before
|
# A#before
|
||||||
|
# # run the action
|
||||||
# A#after
|
# A#after
|
||||||
# B#after
|
# B#after
|
||||||
def append_around_filter(*filters)
|
#
|
||||||
conditions = extract_conditions!(filters)
|
# With around filters which yield to the action block, #before and #after
|
||||||
for filter in filters.flatten
|
# are the code before and after the yield.
|
||||||
ensure_filter_responds_to_before_and_after(filter)
|
def append_around_filter(*filters, &block)
|
||||||
append_before_filter(conditions || {}) { |c| filter.before(c) }
|
filters, conditions = extract_conditions(filters, &block)
|
||||||
prepend_after_filter(conditions || {}) { |c| filter.after(c) }
|
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||||
|
append_filter_to_chain([filter, conditions])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
|
# If you prepend_around_filter A.new, B.new, the filter chain looks like:
|
||||||
# on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
|
|
||||||
# respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
|
|
||||||
#
|
#
|
||||||
# A#before
|
# A#before
|
||||||
# B#before
|
# B#before
|
||||||
|
# # run the action
|
||||||
# B#after
|
# B#after
|
||||||
# A#after
|
# A#after
|
||||||
def prepend_around_filter(*filters)
|
#
|
||||||
for filter in filters.flatten
|
# With around filters which yield to the action block, #before and #after
|
||||||
ensure_filter_responds_to_before_and_after(filter)
|
# are the code before and after the yield.
|
||||||
prepend_before_filter { |c| filter.before(c) }
|
def prepend_around_filter(*filters, &block)
|
||||||
append_after_filter { |c| filter.after(c) }
|
filters, conditions = extract_conditions(filters, &block)
|
||||||
|
filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
|
||||||
|
prepend_filter_to_chain([filter, conditions])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Short-hand for append_around_filter since that's the most common of the two.
|
# Shorthand for append_around_filter since it's the most common.
|
||||||
alias :around_filter :append_around_filter
|
alias :around_filter :append_around_filter
|
||||||
|
|
||||||
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
# Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
|
||||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||||
# of many sub-controllers need a different hierarchy.
|
# of many sub-controllers need a different hierarchy.
|
||||||
#
|
#
|
||||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||||
# just like when you apply the filters.
|
# just like when you apply the filters.
|
||||||
def skip_before_filter(*filters)
|
def skip_before_filter(*filters)
|
||||||
if conditions = extract_conditions!(filters)
|
skip_filter_in_chain(*filters, &:before?)
|
||||||
remove_contradicting_conditions!(filters, conditions)
|
|
||||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
else
|
|
||||||
for filter in filters.flatten
|
|
||||||
write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
# Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
|
||||||
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
# filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
|
||||||
# of many sub-controllers need a different hierarchy.
|
# of many sub-controllers need a different hierarchy.
|
||||||
#
|
#
|
||||||
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||||
# just like when you apply the filters.
|
# just like when you apply the filters.
|
||||||
def skip_after_filter(*filters)
|
def skip_after_filter(*filters)
|
||||||
if conditions = extract_conditions!(filters)
|
skip_filter_in_chain(*filters, &:after?)
|
||||||
remove_contradicting_conditions!(filters, conditions)
|
|
||||||
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
|
||||||
add_action_conditions(filters, conditions)
|
|
||||||
else
|
|
||||||
for filter in filters.flatten
|
|
||||||
write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Removes the specified filters from the filter chain. This only works for method reference (symbol)
|
||||||
|
# filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
|
||||||
|
# it will match any before, after or yielding around filter.
|
||||||
|
#
|
||||||
|
# You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
|
||||||
|
# just like when you apply the filters.
|
||||||
|
def skip_filter(*filters)
|
||||||
|
skip_filter_in_chain(*filters)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns an array of Filter objects for this controller.
|
||||||
|
def filter_chain
|
||||||
|
read_inheritable_attribute("filter_chain") || []
|
||||||
|
end
|
||||||
|
|
||||||
# Returns all the before filters for this class and all its ancestors.
|
# Returns all the before filters for this class and all its ancestors.
|
||||||
|
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||||
def before_filters #:nodoc:
|
def before_filters #:nodoc:
|
||||||
@before_filters ||= read_inheritable_attribute("before_filters") || []
|
filter_chain.select(&:before?).map(&:filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all the after filters for this class and all its ancestors.
|
# Returns all the after filters for this class and all its ancestors.
|
||||||
|
# This method returns the actual filter that was assigned in the controller to maintain existing functionality.
|
||||||
def after_filters #:nodoc:
|
def after_filters #:nodoc:
|
||||||
@after_filters ||= read_inheritable_attribute("after_filters") || []
|
filter_chain.select(&:after?).map(&:filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a mapping between filters and the actions that may run them.
|
# Returns a mapping between filters and the actions that may run them.
|
||||||
def included_actions #:nodoc:
|
def included_actions #:nodoc:
|
||||||
@included_actions ||= read_inheritable_attribute("included_actions") || {}
|
filter_chain.inject({}) { |h,f| h[f.filter] = f.included_actions; h }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a mapping between filters and actions that may not run them.
|
# Returns a mapping between filters and actions that may not run them.
|
||||||
def excluded_actions #:nodoc:
|
def excluded_actions #:nodoc:
|
||||||
@excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
|
filter_chain.inject({}) { |h,f| h[f.filter] = f.excluded_actions; h }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# Find a filter in the filter_chain where the filter method matches the _filter_ param
|
||||||
def append_filter_to_chain(condition, filters)
|
# and (optionally) the passed block evaluates to true (mostly used for testing before?
|
||||||
write_inheritable_array("#{condition}_filters", filters)
|
# and after? on the filter). Useful for symbol filters.
|
||||||
|
#
|
||||||
|
# The object of type Filter is passed to the block when yielded, not the filter itself.
|
||||||
|
def find_filter(filter, &block) #:nodoc:
|
||||||
|
filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
|
||||||
|
end
|
||||||
|
|
||||||
|
# Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
|
||||||
|
# contains no logic for calling the actual filters.
|
||||||
|
class Filter #:nodoc:
|
||||||
|
attr_reader :filter, :included_actions, :excluded_actions
|
||||||
|
|
||||||
|
def initialize(filter, options={})
|
||||||
|
@filter = filter
|
||||||
|
@position = options[:position]
|
||||||
|
update_conditions(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepend_filter_to_chain(condition, filters)
|
def update_conditions(conditions)
|
||||||
old_filters = read_inheritable_attribute("#{condition}_filters") || []
|
if conditions[:only]
|
||||||
write_inheritable_attribute("#{condition}_filters", filters + old_filters)
|
@included_actions = [conditions[:only]].flatten.map(&:to_s)
|
||||||
|
else
|
||||||
|
@excluded_actions = [conditions[:except]].flatten.compact.map(&:to_s)
|
||||||
|
end
|
||||||
|
build_excluded_actions_hash
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_filter_responds_to_before_and_after(filter)
|
def remove_actions_from_included_actions!(*actions)
|
||||||
unless filter.respond_to?(:before) && filter.respond_to?(:after)
|
if @included_actions
|
||||||
raise ActionControllerError, "Filter object must respond to both before and after"
|
actions.flatten.map(&:to_s).each { |action| @included_actions.delete(action) }
|
||||||
|
build_excluded_actions_hash
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_conditions!(filters)
|
def before?
|
||||||
return nil unless filters.last.is_a? Hash
|
false
|
||||||
filters.pop
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_action_conditions(filters, conditions)
|
def after?
|
||||||
return unless conditions
|
false
|
||||||
included, excluded = conditions[:only], conditions[:except]
|
|
||||||
write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
|
|
||||||
write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def condition_hash(filters, *actions)
|
def excluded_from?(action)
|
||||||
filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
|
@excluded_actions_hash[action]
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_contradicting_conditions!(filters, conditions)
|
def call(controller, &block)
|
||||||
return unless conditions[:only]
|
raise(ActionControllerError, 'No filter type: Nothing to do here.')
|
||||||
filters.each do |filter|
|
end
|
||||||
next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
|
|
||||||
[*conditions[:only]].each do |conditional_action|
|
private
|
||||||
conditional_action = conditional_action.to_s
|
def build_excluded_actions_hash
|
||||||
included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
|
@excluded_actions_hash =
|
||||||
|
if @included_actions
|
||||||
|
@included_actions.inject(Hash.new(true)){|h, a| h[a] = false; h}
|
||||||
|
else
|
||||||
|
@excluded_actions.inject(Hash.new(false)){|h, a| h[a] = true; h}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
|
||||||
|
# before_filter and after_filter by moving the logic into the filter itself.
|
||||||
|
class FilterProxy < Filter #:nodoc:
|
||||||
|
def filter
|
||||||
|
@filter.filter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class BeforeFilterProxy < FilterProxy #:nodoc:
|
||||||
|
def before?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(controller, &block)
|
||||||
|
if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
|
||||||
|
controller.halt_filter_chain(@filter, :returned_false)
|
||||||
|
else
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AfterFilterProxy < FilterProxy #:nodoc:
|
||||||
|
def after?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(controller, &block)
|
||||||
|
yield
|
||||||
|
@filter.call(controller)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class SymbolFilter < Filter #:nodoc:
|
||||||
|
def call(controller, &block)
|
||||||
|
controller.send(@filter, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ProcFilter < Filter #:nodoc:
|
||||||
|
def call(controller)
|
||||||
|
@filter.call(controller)
|
||||||
|
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||||
|
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ProcWithCallFilter < Filter #:nodoc:
|
||||||
|
def call(controller, &block)
|
||||||
|
@filter.call(controller, block)
|
||||||
|
rescue LocalJumpError # a yield from a proc... no no bad dog.
|
||||||
|
raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MethodFilter < Filter #:nodoc:
|
||||||
|
def call(controller, &block)
|
||||||
|
@filter.call(controller, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ClassFilter < Filter #:nodoc:
|
||||||
|
def call(controller, &block)
|
||||||
|
@filter.filter(controller, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def append_filter_to_chain(filters, position = :around, &block)
|
||||||
|
write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepend_filter_to_chain(filters, position = :around, &block)
|
||||||
|
write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_filters(filters, position, &block) #:nodoc:
|
||||||
|
filters, conditions = extract_conditions(filters, &block)
|
||||||
|
conditions.merge!(:position => position)
|
||||||
|
|
||||||
|
# conditions should be blank for a filter behind a filter proxy since the proxy will take care of all conditions.
|
||||||
|
proxy_conditions, conditions = conditions, {} unless :around == position
|
||||||
|
|
||||||
|
filters.map do |filter|
|
||||||
|
# change all the filters into instances of Filter derived classes
|
||||||
|
class_for_filter(filter).new(filter, conditions)
|
||||||
|
end.collect do |filter|
|
||||||
|
# apply proxy to filter if necessary
|
||||||
|
case position
|
||||||
|
when :before
|
||||||
|
BeforeFilterProxy.new(filter, proxy_conditions)
|
||||||
|
when :after
|
||||||
|
AfterFilterProxy.new(filter, proxy_conditions)
|
||||||
|
else
|
||||||
|
filter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The determination of the filter type was once done at run time.
|
||||||
|
# This method is here to extract as much logic from the filter run time as possible
|
||||||
|
def class_for_filter(filter) #:nodoc:
|
||||||
|
case
|
||||||
|
when filter.is_a?(Symbol)
|
||||||
|
SymbolFilter
|
||||||
|
when filter.respond_to?(:call)
|
||||||
|
if filter.is_a?(Method)
|
||||||
|
MethodFilter
|
||||||
|
elsif filter.arity == 1
|
||||||
|
ProcFilter
|
||||||
|
else
|
||||||
|
ProcWithCallFilter
|
||||||
|
end
|
||||||
|
when filter.respond_to?(:filter)
|
||||||
|
ClassFilter
|
||||||
|
else
|
||||||
|
raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_responds_to_before_and_after(filter) #:nodoc:
|
||||||
|
filter.respond_to?(:before) && filter.respond_to?(:after)
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_before_and_after_filter(filter) #:nodoc:
|
||||||
|
return filter unless filter_responds_to_before_and_after(filter)
|
||||||
|
Proc.new do |controller, action|
|
||||||
|
unless filter.before(controller) == false
|
||||||
|
begin
|
||||||
|
action.call
|
||||||
|
ensure
|
||||||
|
filter.after(controller)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_conditions(*filters, &block) #:nodoc:
|
||||||
|
filters.flatten!
|
||||||
|
conditions = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||||
|
filters << block if block_given?
|
||||||
|
return filters.flatten, conditions
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_filter_in_chain(*filters, &test) #:nodoc:
|
||||||
|
filters, conditions = extract_conditions(filters)
|
||||||
|
finder_proc = block_given? ?
|
||||||
|
proc { |f| find_filter(f, &test) } :
|
||||||
|
proc { |f| find_filter(f) }
|
||||||
|
|
||||||
|
filters.map(&finder_proc).reject(&:nil?).each do |filter|
|
||||||
|
if conditions.empty?
|
||||||
|
delete_filter_in_chain(filter)
|
||||||
|
else
|
||||||
|
filter.remove_actions_from_included_actions!(conditions[:only] || [])
|
||||||
|
conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
|
||||||
|
filter.update_conditions(conditions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_filter_in_chain(filter) #:nodoc:
|
||||||
|
write_inheritable_attribute('filter_chain', filter_chain.reject { |f| f == filter })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module InstanceMethods # :nodoc:
|
module InstanceMethods # :nodoc:
|
||||||
|
@ -357,14 +609,10 @@ module ActionController #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_action_with_filters
|
def perform_action_with_filters
|
||||||
before_action_result = before_action
|
#result = perform_filters do
|
||||||
|
# perform_action_without_filters unless performed?
|
||||||
unless before_action_result == false || performed?
|
#end
|
||||||
perform_action_without_filters
|
@before_filter_chain_aborted = (call_filter(self.class.filter_chain, 0) == false)
|
||||||
after_action
|
|
||||||
end
|
|
||||||
|
|
||||||
@before_filter_chain_aborted = (before_action_result == false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
|
||||||
|
@ -372,61 +620,37 @@ module ActionController #:nodoc:
|
||||||
process_without_filters(request, response, method, *arguments)
|
process_without_filters(request, response, method, *arguments)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calls all the defined before-filter filters, which are added by using "before_filter :method".
|
def filter_chain
|
||||||
# If any of the filters return false, no more filters will be executed and the action is aborted.
|
self.class.filter_chain
|
||||||
def before_action #:doc:
|
|
||||||
call_filters(self.class.before_filters)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calls all the defined after-filter filters, which are added by using "after_filter :method".
|
def call_filter(chain, index)
|
||||||
# If any of the filters return false, no more filters will be executed.
|
return (performed? || perform_action_without_filters) if index >= chain.size
|
||||||
def after_action #:doc:
|
filter = chain[index]
|
||||||
call_filters(self.class.after_filters)
|
return call_filter(chain, index.next) if filter.excluded_from?(action_name)
|
||||||
|
|
||||||
|
called = false
|
||||||
|
filter.call(self) do
|
||||||
|
call_filter(chain, index.next)
|
||||||
|
called = true
|
||||||
|
end
|
||||||
|
halt_filter_chain(filter.filter, :no_yield) if called == false
|
||||||
|
called
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def halt_filter_chain(filter, reason)
|
||||||
|
if logger
|
||||||
|
case reason
|
||||||
|
when :no_yield
|
||||||
|
logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
|
||||||
|
when :returned_false
|
||||||
|
logger.info "Filter chain halted as [#{filter.inspect}] returned false."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def call_filters(filters)
|
|
||||||
filters.each do |filter|
|
|
||||||
next if action_exempted?(filter)
|
|
||||||
|
|
||||||
filter_result = case
|
|
||||||
when filter.is_a?(Symbol)
|
|
||||||
self.send(filter)
|
|
||||||
when filter_block?(filter)
|
|
||||||
filter.call(self)
|
|
||||||
when filter_class?(filter)
|
|
||||||
filter.filter(self)
|
|
||||||
else
|
|
||||||
raise(
|
|
||||||
ActionControllerError,
|
|
||||||
'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
if filter_result == false
|
|
||||||
logger.info "Filter chain halted as [#{filter}] returned false" if logger
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_block?(filter)
|
|
||||||
filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
def filter_class?(filter)
|
|
||||||
filter.respond_to?('filter')
|
|
||||||
end
|
|
||||||
|
|
||||||
def action_exempted?(filter)
|
|
||||||
case
|
|
||||||
when ia = self.class.included_actions[filter]
|
|
||||||
!ia.include?(action_name)
|
|
||||||
when ea = self.class.excluded_actions[filter]
|
|
||||||
ea.include?(action_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_cleanup_with_filters
|
def process_cleanup_with_filters
|
||||||
if @before_filter_chain_aborted
|
if @before_filter_chain_aborted
|
||||||
close_session
|
close_session
|
||||||
|
|
|
@ -106,6 +106,7 @@ class ActiveRecordTestCase < Test::Unit::TestCase
|
||||||
private
|
private
|
||||||
# If things go wrong, we don't want to run our test cases. We'll just define them to test nothing.
|
# If things go wrong, we don't want to run our test cases. We'll just define them to test nothing.
|
||||||
def abort_tests
|
def abort_tests
|
||||||
|
$stderr.puts 'No Active Record connection, aborting tests.'
|
||||||
self.class.public_instance_methods.grep(/^test./).each do |method|
|
self.class.public_instance_methods.grep(/^test./).each do |method|
|
||||||
self.class.class_eval { define_method(method.to_sym){} }
|
self.class.class_eval { define_method(method.to_sym){} }
|
||||||
end
|
end
|
||||||
|
|
|
@ -198,15 +198,6 @@ class FilterTest < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class BadFilterController < ActionController::Base
|
|
||||||
before_filter 2
|
|
||||||
|
|
||||||
def show() "show" end
|
|
||||||
|
|
||||||
protected
|
|
||||||
def rescue_action(e) raise(e) end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AroundFilterController < PrependingController
|
class AroundFilterController < PrependingController
|
||||||
around_filter AroundFilter.new
|
around_filter AroundFilter.new
|
||||||
end
|
end
|
||||||
|
@ -336,19 +327,20 @@ class FilterTest < Test::Unit::TestCase
|
||||||
assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
|
assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
|
||||||
assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
|
assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_bad_filter
|
def test_bad_filter
|
||||||
assert_raises(ActionController::ActionControllerError) {
|
bad_filter_controller = Class.new(ActionController::Base)
|
||||||
test_process(BadFilterController)
|
assert_raises(ActionController::ActionControllerError) do
|
||||||
}
|
bad_filter_controller.before_filter 2
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_around_filter
|
def test_around_filter
|
||||||
controller = test_process(AroundFilterController)
|
controller = test_process(AroundFilterController)
|
||||||
assert controller.template.assigns["before_ran"]
|
assert controller.template.assigns["before_ran"]
|
||||||
assert controller.template.assigns["after_ran"]
|
assert controller.template.assigns["after_ran"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_having_properties_in_around_filter
|
def test_having_properties_in_around_filter
|
||||||
controller = test_process(AroundFilterController)
|
controller = test_process(AroundFilterController)
|
||||||
assert_equal "before and after", controller.template.assigns["execution_log"]
|
assert_equal "before and after", controller.template.assigns["execution_log"]
|
||||||
|
@ -408,3 +400,230 @@ class FilterTest < Test::Unit::TestCase
|
||||||
controller.process(request, ActionController::TestResponse.new)
|
controller.process(request, ActionController::TestResponse.new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PostsController < ActionController::Base
|
||||||
|
def rescue_action(e); raise e; end
|
||||||
|
|
||||||
|
module AroundExceptions
|
||||||
|
class Error < StandardError ; end
|
||||||
|
class Before < Error ; end
|
||||||
|
class After < Error ; end
|
||||||
|
end
|
||||||
|
include AroundExceptions
|
||||||
|
|
||||||
|
class DefaultFilter
|
||||||
|
include AroundExceptions
|
||||||
|
end
|
||||||
|
|
||||||
|
module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n")
|
||||||
|
|
||||||
|
private
|
||||||
|
def default_action
|
||||||
|
render :inline => "#{action_name} called"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithSymbolAsFilter < PostsController
|
||||||
|
around_filter :raise_before, :only => :raises_before
|
||||||
|
around_filter :raise_after, :only => :raises_after
|
||||||
|
around_filter :without_exception, :only => :no_raise
|
||||||
|
|
||||||
|
private
|
||||||
|
def raise_before
|
||||||
|
raise Before
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
|
||||||
|
def raise_after
|
||||||
|
yield
|
||||||
|
raise After
|
||||||
|
end
|
||||||
|
|
||||||
|
def without_exception
|
||||||
|
# Do stuff...
|
||||||
|
1 + 1
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
# Do stuff...
|
||||||
|
1 + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithFilterClass < PostsController
|
||||||
|
class YieldingFilter < DefaultFilter
|
||||||
|
def self.filter(controller)
|
||||||
|
yield
|
||||||
|
raise After
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
around_filter YieldingFilter, :only => :raises_after
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithFilterInstance < PostsController
|
||||||
|
class YieldingFilter < DefaultFilter
|
||||||
|
def filter(controller)
|
||||||
|
yield
|
||||||
|
raise After
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
around_filter YieldingFilter.new, :only => :raises_after
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithFilterMethod < PostsController
|
||||||
|
class YieldingFilter < DefaultFilter
|
||||||
|
def filter(controller)
|
||||||
|
yield
|
||||||
|
raise After
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
around_filter YieldingFilter.new.method(:filter), :only => :raises_after
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithProcFilter < PostsController
|
||||||
|
around_filter(:only => :no_raise) do |c,b|
|
||||||
|
c.assigns['before'] = true
|
||||||
|
b.call
|
||||||
|
c.assigns['after'] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithWrongFilterType < PostsController
|
||||||
|
around_filter lambda { yield }, :only => :no_raise
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithNestedFilters < ControllerWithSymbolAsFilter
|
||||||
|
around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithAllTypesOfFilters < PostsController
|
||||||
|
before_filter :before
|
||||||
|
around_filter :around
|
||||||
|
after_filter :after
|
||||||
|
around_filter :around_again
|
||||||
|
|
||||||
|
private
|
||||||
|
def before
|
||||||
|
@ran_filter ||= []
|
||||||
|
@ran_filter << 'before'
|
||||||
|
end
|
||||||
|
|
||||||
|
def around
|
||||||
|
@ran_filter << 'around (before yield)'
|
||||||
|
yield
|
||||||
|
@ran_filter << 'around (after yield)'
|
||||||
|
end
|
||||||
|
|
||||||
|
def after
|
||||||
|
@ran_filter << 'after'
|
||||||
|
end
|
||||||
|
|
||||||
|
def around_again
|
||||||
|
@ran_filter << 'around_again (before yield)'
|
||||||
|
yield
|
||||||
|
@ran_filter << 'around_again (after yield)'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
|
||||||
|
skip_filter :around_again
|
||||||
|
skip_filter :after
|
||||||
|
end
|
||||||
|
|
||||||
|
class YieldingAroundFiltersTest < Test::Unit::TestCase
|
||||||
|
include PostsController::AroundExceptions
|
||||||
|
|
||||||
|
def test_filters_registering
|
||||||
|
assert_equal 1, ControllerWithFilterMethod.filter_chain.size
|
||||||
|
assert_equal 1, ControllerWithFilterClass.filter_chain.size
|
||||||
|
assert_equal 1, ControllerWithFilterInstance.filter_chain.size
|
||||||
|
assert_equal 3, ControllerWithSymbolAsFilter.filter_chain.size
|
||||||
|
assert_equal 1, ControllerWithWrongFilterType.filter_chain.size
|
||||||
|
assert_equal 6, ControllerWithNestedFilters.filter_chain.size
|
||||||
|
assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_wrong_filter_type
|
||||||
|
assert_raise(ActionController::ActionControllerError) do
|
||||||
|
test_process(ControllerWithWrongFilterType,'no_raise')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_base
|
||||||
|
controller = PostsController
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
assert_nothing_raised { test_process(controller,'raises_before') }
|
||||||
|
assert_nothing_raised { test_process(controller,'raises_after') }
|
||||||
|
assert_nothing_raised { test_process(controller,'no_filter') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_symbol
|
||||||
|
controller = ControllerWithSymbolAsFilter
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
assert_raise(Before) { test_process(controller,'raises_before') }
|
||||||
|
assert_raise(After) { test_process(controller,'raises_after') }
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_class
|
||||||
|
controller = ControllerWithFilterClass
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
assert_raise(After) { test_process(controller,'raises_after') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_instance
|
||||||
|
controller = ControllerWithFilterInstance
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
assert_raise(After) { test_process(controller,'raises_after') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_method
|
||||||
|
controller = ControllerWithFilterMethod
|
||||||
|
assert_nothing_raised { test_process(controller,'no_raise') }
|
||||||
|
assert_raise(After) { test_process(controller,'raises_after') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_proc
|
||||||
|
controller = test_process(ControllerWithProcFilter,'no_raise')
|
||||||
|
assert controller.template.assigns['before']
|
||||||
|
assert controller.template.assigns['after']
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_nested_filters
|
||||||
|
controller = ControllerWithNestedFilters
|
||||||
|
assert_nothing_raised do
|
||||||
|
begin
|
||||||
|
test_process(controller,'raises_both')
|
||||||
|
rescue Before, After
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert_raise Before do
|
||||||
|
begin
|
||||||
|
test_process(controller,'raises_both')
|
||||||
|
rescue After
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_filter_order_with_all_filter_types
|
||||||
|
controller = test_process(ControllerWithAllTypesOfFilters,'no_raise')
|
||||||
|
assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after',controller.template.assigns['ran_filter'].join(' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_filter_order_with_skip_filter_method
|
||||||
|
controller = test_process(ControllerWithTwoLessFilters,'no_raise')
|
||||||
|
assert_equal 'before around (before yield) around (after yield)',controller.template.assigns['ran_filter'].join(' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
def test_process(controller, action = "show")
|
||||||
|
request = ActionController::TestRequest.new
|
||||||
|
request.action = action
|
||||||
|
controller.process(request, ActionController::TestResponse.new)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue