~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_controller/filters.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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