Froze rails gems
[depot.git] / vendor / rails / actionpack / lib / action_controller / filters.rb
1 module ActionController #:nodoc:
2 module Filters #:nodoc:
3 def self.included(base)
4 base.class_eval do
5 extend ClassMethods
6 include ActionController::Filters::InstanceMethods
7 end
8 end
9
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)
14 end
15
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)
19 end
20
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) }
24 filters
25 end
26
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)
33 end
34
35 private
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!
39 end
40
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|
46 return i if f.after?
47 end
48 end
49 return -1
50 end
51
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|
57 return i if f.after?
58 end
59 return -1
60 end
61 return 0
62 end
63
64 def find_or_create_filter(filter, filter_type, options = {})
65 update_filter_in_chain([filter], options)
66
67 if found_filter = find(filter) { |f| f.type == filter_type }
68 found_filter
69 else
70 filter_kind = case
71 when filter.respond_to?(:before) && filter_type == :before
72 :before
73 when filter.respond_to?(:after) && filter_type == :after
74 :after
75 else
76 :filter
77 end
78
79 case filter_type
80 when :before
81 BeforeFilter.new(filter_kind, filter, options)
82 when :after
83 AfterFilter.new(filter_kind, filter, options)
84 else
85 AroundFilter.new(filter_kind, filter, options)
86 end
87 end
88 end
89
90 def update_filter_in_chain(filters, options, &test)
91 filters.map! { |f| block_given? ? find(f, &test) : find(f) }
92 filters.compact!
93
94 map! do |filter|
95 if filters.include?(filter)
96 new_filter = filter.dup
97 new_filter.update_options!(options)
98 new_filter
99 else
100 filter
101 end
102 end
103 end
104 end
105
106 class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
107 def initialize(kind, method, options = {})
108 super
109 update_options! options
110 end
111
112 # override these to return true in appropriate subclass
113 def before?
114 false
115 end
116
117 def after?
118 false
119 end
120
121 def around?
122 false
123 end
124
125 # Make sets of strings from :only/:except options
126 def update_options!(other)
127 if other
128 convert_only_and_except_options_to_sets_of_strings(other)
129 if other[:skip]
130 convert_only_and_except_options_to_sets_of_strings(other[:skip])
131 end
132 end
133
134 options.update(other)
135 end
136
137 private
138 def should_not_skip?(controller)
139 if options[:skip]
140 !included_in_action?(controller, options[:skip])
141 else
142 true
143 end
144 end
145
146 def included_in_action?(controller, options)
147 if options[:only]
148 options[:only].include?(controller.action_name)
149 elsif options[:except]
150 !options[:except].include?(controller.action_name)
151 else
152 true
153 end
154 end
155
156 def should_run_callback?(controller)
157 should_not_skip?(controller) && included_in_action?(controller, options) && super
158 end
159
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
164 end
165 end
166 end
167 end
168
169 class AroundFilter < Filter #:nodoc:
170 def type
171 :around
172 end
173
174 def around?
175 true
176 end
177
178 def call(controller, &block)
179 if should_run_callback?(controller)
180 method = filter_responds_to_before_and_after? ? around_proc : self.method
181
182 # For around_filter do |controller, action|
183 if method.is_a?(Proc) && method.arity == 2
184 evaluate_method(method, controller, block)
185 else
186 evaluate_method(method, controller, &block)
187 end
188 else
189 block.call
190 end
191 end
192
193 private
194 def filter_responds_to_before_and_after?
195 method.respond_to?(:before) && method.respond_to?(:after)
196 end
197
198 def around_proc
199 Proc.new do |controller, action|
200 method.before(controller)
201
202 if controller.__send__(:performed?)
203 controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
204 else
205 begin
206 action.call
207 ensure
208 method.after(controller)
209 end
210 end
211 end
212 end
213 end
214
215 class BeforeFilter < Filter #:nodoc:
216 def type
217 :before
218 end
219
220 def before?
221 true
222 end
223
224 def call(controller, &block)
225 super
226 if controller.__send__(:performed?)
227 controller.__send__(:halt_filter_chain, method, :rendered_or_redirected)
228 end
229 end
230 end
231
232 class AfterFilter < Filter #:nodoc:
233 def type
234 :after
235 end
236
237 def after?
238 true
239 end
240 end
241
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).
246 #
247 # == Filter inheritance
248 #
249 # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
250 # affecting the superclass. For example:
251 #
252 # class BankController < ActionController::Base
253 # before_filter :audit
254 #
255 # private
256 # def audit
257 # # record the action and parameters in an audit log
258 # end
259 # end
260 #
261 # class VaultController < BankController
262 # before_filter :verify_credentials
263 #
264 # private
265 # def verify_credentials
266 # # make sure the user is allowed into the vault
267 # end
268 # end
269 #
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.
273 #
274 # == Filter types
275 #
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.
279 #
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:
282 #
283 # class OutputCompressionFilter
284 # def self.filter(controller)
285 # controller.response.body = compress(controller.response.body)
286 # end
287 # end
288 #
289 # class NewspaperController < ActionController::Base
290 # after_filter OutputCompressionFilter
291 # end
292 #
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.
295 #
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:
298 #
299 # class WeblogController < ActionController::Base
300 # before_filter { |controller| head(400) if controller.params["stop_action"] }
301 # end
302 #
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).
307 #
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.
310 #
311 # == Filter chain ordering
312 #
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:
317 #
318 # class ShoppingController < ActionController::Base
319 # before_filter :verify_open_shop
320 #
321 # class CheckoutController < ShoppingController
322 # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
323 #
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
326 # is open or not.
327 #
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.
330 #
331 # == Around filters
332 #
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+.
336 #
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.
339 #
340 # around_filter :catch_exceptions
341 #
342 # private
343 # def catch_exceptions
344 # yield
345 # rescue => exception
346 # logger.debug "Caught exception! #{exception}"
347 # raise
348 # end
349 #
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:
353 #
354 # around_filter do |controller, action|
355 # logger.debug "before #{controller.action_name}"
356 # action.call
357 # logger.debug "after #{controller.action_name}"
358 # end
359 #
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:
363 #
364 # around_filter BenchmarkingFilter
365 #
366 # class BenchmarkingFilter
367 # def self.filter(controller, &block)
368 # Benchmark.measure(&block)
369 # end
370 # end
371 #
372 # With +before+ and +after+ methods:
373 #
374 # around_filter Authorizer.new
375 #
376 # class Authorizer
377 # # This will run before the action. Redirecting aborts the action.
378 # def before(controller)
379 # unless user.authorized?
380 # redirect_to(login_url)
381 # end
382 # end
383 #
384 # # This will run after the action if and only if before did not render or redirect.
385 # def after(controller)
386 # end
387 # end
388 #
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
392 # an example.
393 #
394 # == Filter chain skipping
395 #
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:
398 #
399 # class ApplicationController < ActionController::Base
400 # before_filter :authenticate
401 # around_filter :catch_exceptions
402 # end
403 #
404 # class WeblogController < ApplicationController
405 # # Will run the :authenticate and :catch_exceptions filters.
406 # end
407 #
408 # class SignupController < ApplicationController
409 # # Skip :authenticate, run :catch_exceptions.
410 # skip_before_filter :authenticate
411 # end
412 #
413 # class ProjectsController < ApplicationController
414 # # Skip :catch_exceptions, run :authenticate.
415 # skip_filter :catch_exceptions
416 # end
417 #
418 # class ClientsController < ApplicationController
419 # # Skip :catch_exceptions and :authenticate unless action is index.
420 # skip_filter :catch_exceptions, :authenticate, :except => :index
421 # end
422 #
423 # == Filter conditions
424 #
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>).
429 #
430 # class Journal < ActionController::Base
431 # # Require authentication for edit and delete.
432 # before_filter :authorize, :only => [:edit, :delete]
433 #
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>"
438 # end
439 #
440 # private
441 # def authorize
442 # # Redirect to login unless authenticated.
443 # end
444 # end
445 #
446 # == Filter Chain Halting
447 #
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
452 # chain is halted.
453 #
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
459 #
460 # The filter chain will look like:
461 #
462 # ...
463 # . \
464 # . #around (code before yield)
465 # . . \
466 # . . #before (actual filter code is run)
467 # . . . \
468 # . . . execute controller action
469 # . . . /
470 # . . ...
471 # . . /
472 # . #around (code after yield)
473 # . /
474 # #after (actual filter code is run, unless the around filter does not yield)
475 #
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.
480 module ClassMethods
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)
485 end
486
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)
491 end
492
493 # Shorthand for append_before_filter since it's the most common.
494 alias :before_filter :append_before_filter
495
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)
500 end
501
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)
506 end
507
508 # Shorthand for append_after_filter since it's the most common.
509 alias :after_filter :append_after_filter
510
511 # If you <tt>append_around_filter A.new, B.new</tt>, the filter chain looks like
512 #
513 # B#before
514 # A#before
515 # # run the action
516 # A#after
517 # B#after
518 #
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)
523 end
524
525 # If you <tt>prepend_around_filter A.new, B.new</tt>, the filter chain looks like:
526 #
527 # A#before
528 # B#before
529 # # run the action
530 # B#after
531 # A#after
532 #
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)
537 end
538
539 # Shorthand for +append_around_filter+ since it's the most common.
540 alias :around_filter :append_around_filter
541
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.
545 #
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?)
550 end
551
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.
555 #
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?)
560 end
561
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.
565 #
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)
570 end
571
572 # Returns an array of Filter objects for this controller.
573 def filter_chain
574 if chain = read_inheritable_attribute('filter_chain')
575 return chain
576 else
577 write_inheritable_attribute('filter_chain', FilterChain.new)
578 return filter_chain
579 end
580 end
581
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)
586 end
587
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)
592 end
593 end
594
595 module InstanceMethods # :nodoc:
596 def self.included(base)
597 base.class_eval do
598 alias_method_chain :perform_action, :filters
599 alias_method_chain :process, :filters
600 end
601 end
602
603 protected
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)
607 end
608
609 def perform_action_with_filters
610 call_filters(self.class.filter_chain, 0, 0)
611 end
612
613 private
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)
620 end
621
622 def run_before_filters(chain, index, nesting)
623 while chain[index]
624 filter, index = chain[index], index
625 break unless filter # end of call chain reached
626
627 case filter
628 when BeforeFilter
629 filter.call(self) # invoke before filter
630 index = index.next
631 break if @before_filter_chain_aborted
632 when AroundFilter
633 yielded = false
634
635 filter.call(self) do
636 yielded = true
637 # all remaining before and around filters will be run in this call
638 index = call_filters(chain, index.next, nesting.next)
639 end
640
641 halt_filter_chain(filter, :did_not_yield) unless yielded
642
643 break
644 else
645 break # no before or around filters left
646 end
647 end
648
649 index
650 end
651
652 def run_after_filters(chain, index)
653 seen_after_filter = false
654
655 while chain[index]
656 filter, index = chain[index], index
657 break unless filter # end of call chain reached
658
659 case filter
660 when AfterFilter
661 seen_after_filter = true
662 filter.call(self) # invoke after filter
663 else
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
666 end
667
668 index = index.next
669 end
670
671 index.next
672 end
673
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
677 end
678 end
679 end
680 end