1 module ActionController
#:nodoc:
2 module Filters
#:nodoc:
3 def self.included(base
)
6 include ActionController
::Filters::InstanceMethods
10 class FilterChain
< ActiveSupport
::Callbacks::CallbackChain #:nodoc:
11 def append_filter_to_chain(filters
, filter_type
, &block
)
12 pos
= find_filter_append_position(filters
, filter_type
)
13 update_filter_chain(filters
, filter_type
, pos
, &block
)
16 def prepend_filter_to_chain(filters
, filter_type
, &block
)
17 pos
= find_filter_prepend_position(filters
, filter_type
)
18 update_filter_chain(filters
, filter_type
, pos
, &block
)
21 def create_filters(filters
, filter_type
, &block
)
22 filters
, conditions
= extract_options(filters
, &block
)
23 filters
.map
! { |filter
| find_or_create_filter(filter
, filter_type
, conditions
) }
27 def skip_filter_in_chain(*filters
, &test
)
28 filters
, conditions
= extract_options(filters
)
29 filters
.each
do |filter
|
30 if callback
= find(filter
) then delete(callback
) end
31 end if conditions
.empty
?
32 update_filter_in_chain(filters
, :skip => conditions
, &test
)
36 def update_filter_chain(filters
, filter_type
, pos
, &block
)
37 new_filters
= create_filters(filters
, filter_type
, &block
)
38 insert(pos
, new_filters
).flatten
!
41 def find_filter_append_position(filters
, filter_type
)
42 # appending an after filter puts it at the end of the call chain
43 # before and around filters go before the first after filter in the chain
44 unless filter_type
== :after
45 each_with_index
do |f
,i
|
52 def find_filter_prepend_position(filters
, filter_type
)
53 # prepending a before or around filter puts it at the front of the call chain
54 # after filters go before the first after filter in the chain
55 if filter_type
== :after
56 each_with_index
do |f
,i
|
64 def find_or_create_filter(filter
, filter_type
, options
= {})
65 update_filter_in_chain([filter
], options
)
67 if found_filter
= find(filter
) { |f
| f
.type
== filter_type
}
71 when filter
.respond_to
?(:before) && filter_type
== :before
73 when filter
.respond_to
?(:after) && filter_type
== :after
81 BeforeFilter
.new(filter_kind
, filter
, options
)
83 AfterFilter
.new(filter_kind
, filter
, options
)
85 AroundFilter
.new(filter_kind
, filter
, options
)
90 def update_filter_in_chain(filters
, options
, &test
)
91 filters
.map
! { |f
| block_given
? ? find(f
, &test
) : find(f
) }
95 if filters
.include?(filter
)
96 new_filter
= filter
.dup
97 new_filter
.update_options
!(options
)
106 class Filter
< ActiveSupport
::Callbacks::Callback #:nodoc:
107 def initialize(kind
, method
, options
= {})
109 update_options
! options
112 # override these to return true in appropriate subclass
125 # Make sets of strings from :only/:except options
126 def update_options
!(other
)
128 convert_only_and_except_options_to_sets_of_strings(other
)
130 convert_only_and_except_options_to_sets_of_strings(other
[:skip])
134 options
.update(other
)
138 def should_not_skip
?(controller
)
140 !included_in_action
?(controller
, options
[:skip])
146 def included_in_action
?(controller
, options
)
148 options
[:only].include?(controller
.action_name
)
149 elsif options
[:except]
150 !options
[:except].include?(controller
.action_name
)
156 def should_run_callback
?(controller
)
157 should_not_skip
?(controller
) && included_in_action
?(controller
, options
) && super
160 def convert_only_and_except_options_to_sets_of_strings(opts
)
161 [:only, :except].each
do |key
|
162 if values
= opts
[key
]
163 opts
[key
] = Array(values
).map(&:to_s).to_set
169 class AroundFilter
< Filter
#:nodoc:
178 def call(controller
, &block
)
179 if should_run_callback
?(controller
)
180 method
= filter_responds_to_before_and_after
? ? around_proc
: self.method
182 # For around_filter do |controller, action|
183 if method
.is_a
?(Proc
) && method
.arity
== 2
184 evaluate_method(method
, controller
, block
)
186 evaluate_method(method
, controller
, &block
)
194 def filter_responds_to_before_and_after
?
195 method
.respond_to
?(:before) && method
.respond_to
?(:after)
199 Proc
.new
do |controller
, action
|
200 method
.before(controller
)
202 if controller
.__send__(:performed?)
203 controller
.__send__(:halt_filter_chain, method
, :rendered_or_redirected)
208 method
.after(controller
)
215 class BeforeFilter
< Filter
#:nodoc:
224 def call(controller
, &block
)
226 if controller
.__send__(:performed?)
227 controller
.__send__(:halt_filter_chain, method
, :rendered_or_redirected)
232 class AfterFilter
< Filter
#:nodoc:
242 # Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
243 # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
244 # compression after the action has been performed. Filters have access to the request, response, and all the instance
245 # variables set by other filters in the chain or by the action (in the case of after filters).
247 # == Filter inheritance
249 # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
250 # affecting the superclass. For example:
252 # class BankController < ActionController::Base
253 # before_filter :audit
257 # # record the action and parameters in an audit log
261 # class VaultController < BankController
262 # before_filter :verify_credentials
265 # def verify_credentials
266 # # make sure the user is allowed into the vault
270 # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
271 # first the audit method is called, then the verify_credentials method. If the audit method renders or redirects, then
272 # verify_credentials and the intended action are never called.
276 # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
277 # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
278 # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
280 # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
281 # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
283 # class OutputCompressionFilter
284 # def self.filter(controller)
285 # controller.response.body = compress(controller.response.body)
289 # class NewspaperController < ActionController::Base
290 # after_filter OutputCompressionFilter
293 # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
294 # manipulate them as it sees fit.
296 # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
297 # Or just as a quick test. It works like this:
299 # class WeblogController < ActionController::Base
300 # before_filter { |controller| head(400) if controller.params["stop_action"] }
303 # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
304 # This means that the block has access to both the request and response objects complete with convenience methods for params,
305 # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
306 # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
308 # Please note that around_filters function a little differently than the normal before and after filters with regard to filter
309 # types. Please see the section dedicated to around_filters below.
311 # == Filter chain ordering
313 # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
314 # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
315 # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
316 # beginning of their respective chain and executed before the rest. For example:
318 # class ShoppingController < ActionController::Base
319 # before_filter :verify_open_shop
321 # class CheckoutController < ShoppingController
322 # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
324 # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
325 # <tt>:verify_open_shop</tt>. So if either of the ensure filters renders or redirects, we'll never get around to see if the shop
328 # You may pass multiple filter arguments of each type as well as a filter block.
329 # If a block is given, it is treated as the last argument.
333 # Around filters wrap an action, executing code both before and after.
334 # They may be declared as method references, blocks, or objects responding
335 # to +filter+ or to both +before+ and +after+.
337 # To use a method as an +around_filter+, pass a symbol naming the Ruby method.
338 # Yield (or <tt>block.call</tt>) within the method to run the action.
340 # around_filter :catch_exceptions
343 # def catch_exceptions
345 # rescue => exception
346 # logger.debug "Caught exception! #{exception}"
350 # To use a block as an +around_filter+, pass a block taking as args both
351 # the controller and the action block. You can't call yield directly from
352 # an +around_filter+ block; explicitly call the action block instead:
354 # around_filter do |controller, action|
355 # logger.debug "before #{controller.action_name}"
357 # logger.debug "after #{controller.action_name}"
360 # To use a filter object with +around_filter+, pass an object responding
361 # to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
362 # filter method, yield to the block as above:
364 # around_filter BenchmarkingFilter
366 # class BenchmarkingFilter
367 # def self.filter(controller, &block)
368 # Benchmark.measure(&block)
372 # With +before+ and +after+ methods:
374 # around_filter Authorizer.new
377 # # This will run before the action. Redirecting aborts the action.
378 # def before(controller)
379 # unless user.authorized?
380 # redirect_to(login_url)
384 # # This will run after the action if and only if before did not render or redirect.
385 # def after(controller)
389 # If the filter has +before+ and +after+ methods, the +before+ method will be
390 # called before the action. If +before+ renders or redirects, the filter chain is
391 # halted and +after+ will not be run. See Filter Chain Halting below for
394 # == Filter chain skipping
396 # Declaring a filter on a base class conveniently applies to its subclasses,
397 # but sometimes a subclass should skip some of its superclass' filters:
399 # class ApplicationController < ActionController::Base
400 # before_filter :authenticate
401 # around_filter :catch_exceptions
404 # class WeblogController < ApplicationController
405 # # Will run the :authenticate and :catch_exceptions filters.
408 # class SignupController < ApplicationController
409 # # Skip :authenticate, run :catch_exceptions.
410 # skip_before_filter :authenticate
413 # class ProjectsController < ApplicationController
414 # # Skip :catch_exceptions, run :authenticate.
415 # skip_filter :catch_exceptions
418 # class ClientsController < ApplicationController
419 # # Skip :catch_exceptions and :authenticate unless action is index.
420 # skip_filter :catch_exceptions, :authenticate, :except => :index
423 # == Filter conditions
425 # Filters may be limited to specific actions by declaring the actions to
426 # include or exclude. Both options accept single actions
427 # (<tt>:only => :index</tt>) or arrays of actions
428 # (<tt>:except => [:foo, :bar]</tt>).
430 # class Journal < ActionController::Base
431 # # Require authentication for edit and delete.
432 # before_filter :authorize, :only => [:edit, :delete]
434 # # Passing options to a filter with a block.
435 # around_filter(:except => :index) do |controller, action_block|
436 # results = Profiler.run(&action_block)
437 # controller.response.sub! "</body>", "#{results}</body>"
442 # # Redirect to login unless authenticated.
446 # == Filter Chain Halting
448 # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
449 # before a controller action is run. This is useful, for example, to deny
450 # access to unauthenticated users or to redirect from HTTP to HTTPS.
451 # Simply call render or redirect. After filters will not be executed if the filter
454 # Around filters halt the request unless the action block is called.
455 # Given these filters
456 # after_filter :after
457 # around_filter :around
458 # before_filter :before
460 # The filter chain will look like:
464 # . #around (code before yield)
466 # . . #before (actual filter code is run)
468 # . . . execute controller action
472 # . #around (code after yield)
474 # #after (actual filter code is run, unless the around filter does not yield)
476 # If +around+ returns before yielding, +after+ will still not be run. The +before+
477 # filter and controller action will not be run. If +before+ renders or redirects,
478 # the second half of +around+ and will still run but +after+ and the
479 # action will not. If +around+ fails to yield, +after+ will not be run.
481 # The passed <tt>filters</tt> will be appended to the filter_chain and
482 # will execute before the action on this controller is performed.
483 def append_before_filter(*filters
, &block
)
484 filter_chain
.append_filter_to_chain(filters
, :before, &block
)
487 # The passed <tt>filters</tt> will be prepended to the filter_chain and
488 # will execute before the action on this controller is performed.
489 def prepend_before_filter(*filters
, &block
)
490 filter_chain
.prepend_filter_to_chain(filters
, :before, &block
)
493 # Shorthand for append_before_filter since it's the most common.
494 alias :before_filter :append_before_filter
496 # The passed <tt>filters</tt> will be appended to the array of filters
497 # that run _after_ actions on this controller are performed.
498 def append_after_filter(*filters
, &block
)
499 filter_chain
.append_filter_to_chain(filters
, :after, &block
)
502 # The passed <tt>filters</tt> will be prepended to the array of filters
503 # that run _after_ actions on this controller are performed.
504 def prepend_after_filter(*filters
, &block
)
505 filter_chain
.prepend_filter_to_chain(filters
, :after, &block
)
508 # Shorthand for append_after_filter since it's the most common.
509 alias :after_filter :append_after_filter
511 # If you <tt>append_around_filter A.new, B.new</tt>, the filter chain looks like
519 # With around filters which yield to the action block, +before+ and +after+
520 # are the code before and after the yield.
521 def append_around_filter(*filters
, &block
)
522 filter_chain
.append_filter_to_chain(filters
, :around, &block
)
525 # If you <tt>prepend_around_filter A.new, B.new</tt>, the filter chain looks like:
533 # With around filters which yield to the action block, +before+ and +after+
534 # are the code before and after the yield.
535 def prepend_around_filter(*filters
, &block
)
536 filter_chain
.prepend_filter_to_chain(filters
, :around, &block
)
539 # Shorthand for +append_around_filter+ since it's the most common.
540 alias :around_filter :append_around_filter
542 # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
543 # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
544 # of many sub-controllers need a different hierarchy.
546 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
547 # just like when you apply the filters.
548 def skip_before_filter(*filters
)
549 filter_chain
.skip_filter_in_chain(*filters
, &:before?)
552 # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
553 # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
554 # of many sub-controllers need a different hierarchy.
556 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
557 # just like when you apply the filters.
558 def skip_after_filter(*filters
)
559 filter_chain
.skip_filter_in_chain(*filters
, &:after?)
562 # Removes the specified filters from the filter chain. This only works for method reference (symbol)
563 # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
564 # it will match any before, after or yielding around filter.
566 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
567 # just like when you apply the filters.
568 def skip_filter(*filters
)
569 filter_chain
.skip_filter_in_chain(*filters
)
572 # Returns an array of Filter objects for this controller.
574 if chain
= read_inheritable_attribute('filter_chain')
577 write_inheritable_attribute('filter_chain', FilterChain
.new
)
582 # Returns all the before filters for this class and all its ancestors.
583 # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
584 def before_filters
#:nodoc:
585 filter_chain
.select(&:before?).map(&:method)
588 # Returns all the after filters for this class and all its ancestors.
589 # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
590 def after_filters
#:nodoc:
591 filter_chain
.select(&:after?).map(&:method)
595 module InstanceMethods
# :nodoc:
596 def self.included(base
)
598 alias_method_chain
:perform_action, :filters
599 alias_method_chain
:process, :filters
604 def process_with_filters(request
, response
, method
= :perform_action, *arguments
) #:nodoc:
605 @before_filter_chain_aborted = false
606 process_without_filters(request
, response
, method
, *arguments
)
609 def perform_action_with_filters
610 call_filters(self.class.filter_chain
, 0, 0)
614 def call_filters(chain
, index
, nesting
)
615 index
= run_before_filters(chain
, index
, nesting
)
616 aborted
= @before_filter_chain_aborted
617 perform_action_without_filters
unless performed
? || aborted
618 return index
if nesting
!= 0 || aborted
619 run_after_filters(chain
, index
)
622 def run_before_filters(chain
, index
, nesting
)
624 filter
, index
= chain
[index
], index
625 break unless filter
# end of call chain reached
629 filter
.call(self) # invoke before filter
631 break if @before_filter_chain_aborted
637 # all remaining before and around filters will be run in this call
638 index
= call_filters(chain
, index
.next, nesting
.next)
641 halt_filter_chain(filter
, :did_not_yield) unless yielded
645 break # no before or around filters left
652 def run_after_filters(chain
, index
)
653 seen_after_filter
= false
656 filter
, index
= chain
[index
], index
657 break unless filter
# end of call chain reached
661 seen_after_filter
= true
662 filter
.call(self) # invoke after filter
664 # implementation error or someone has mucked with the filter chain
665 raise ActionControllerError
, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
674 def halt_filter_chain(filter
, reason
)
675 @before_filter_chain_aborted = true
676 logger
.info
"Filter chain halted as [#{filter.inspect}] #{reason}." if logger