actionpack 2.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. data/CHANGELOG +149 -7
  2. data/MIT-LICENSE +1 -1
  3. data/README +1 -1
  4. data/Rakefile +5 -6
  5. data/lib/action_controller.rb +2 -2
  6. data/lib/action_controller/assertions/model_assertions.rb +2 -1
  7. data/lib/action_controller/assertions/response_assertions.rb +4 -2
  8. data/lib/action_controller/assertions/routing_assertions.rb +3 -3
  9. data/lib/action_controller/assertions/selector_assertions.rb +30 -27
  10. data/lib/action_controller/assertions/tag_assertions.rb +3 -3
  11. data/lib/action_controller/base.rb +103 -129
  12. data/lib/action_controller/benchmarking.rb +3 -3
  13. data/lib/action_controller/caching.rb +41 -652
  14. data/lib/action_controller/caching/actions.rb +144 -0
  15. data/lib/action_controller/caching/fragments.rb +138 -0
  16. data/lib/action_controller/caching/pages.rb +154 -0
  17. data/lib/action_controller/caching/sql_cache.rb +18 -0
  18. data/lib/action_controller/caching/sweeping.rb +97 -0
  19. data/lib/action_controller/cgi_ext/cookie.rb +27 -23
  20. data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
  21. data/lib/action_controller/cgi_process.rb +6 -4
  22. data/lib/action_controller/components.rb +7 -6
  23. data/lib/action_controller/cookies.rb +31 -19
  24. data/lib/action_controller/dispatcher.rb +51 -84
  25. data/lib/action_controller/filters.rb +295 -421
  26. data/lib/action_controller/flash.rb +1 -6
  27. data/lib/action_controller/headers.rb +31 -0
  28. data/lib/action_controller/helpers.rb +26 -9
  29. data/lib/action_controller/http_authentication.rb +1 -1
  30. data/lib/action_controller/integration.rb +65 -13
  31. data/lib/action_controller/layout.rb +24 -39
  32. data/lib/action_controller/mime_responds.rb +7 -3
  33. data/lib/action_controller/mime_type.rb +25 -9
  34. data/lib/action_controller/mime_types.rb +1 -1
  35. data/lib/action_controller/polymorphic_routes.rb +32 -17
  36. data/lib/action_controller/record_identifier.rb +10 -4
  37. data/lib/action_controller/request.rb +46 -30
  38. data/lib/action_controller/request_forgery_protection.rb +10 -9
  39. data/lib/action_controller/request_profiler.rb +29 -8
  40. data/lib/action_controller/rescue.rb +24 -24
  41. data/lib/action_controller/resources.rb +66 -23
  42. data/lib/action_controller/response.rb +2 -2
  43. data/lib/action_controller/routing.rb +113 -1229
  44. data/lib/action_controller/routing/builder.rb +204 -0
  45. data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
  46. data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
  47. data/lib/action_controller/routing/route.rb +240 -0
  48. data/lib/action_controller/routing/route_set.rb +435 -0
  49. data/lib/action_controller/routing/routing_ext.rb +46 -0
  50. data/lib/action_controller/routing/segments.rb +283 -0
  51. data/lib/action_controller/session/active_record_store.rb +13 -8
  52. data/lib/action_controller/session/cookie_store.rb +20 -17
  53. data/lib/action_controller/session_management.rb +10 -3
  54. data/lib/action_controller/streaming.rb +45 -31
  55. data/lib/action_controller/test_case.rb +33 -23
  56. data/lib/action_controller/test_process.rb +39 -35
  57. data/lib/action_controller/url_rewriter.rb +18 -12
  58. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
  59. data/lib/action_pack.rb +1 -1
  60. data/lib/action_pack/version.rb +2 -2
  61. data/lib/action_view.rb +11 -3
  62. data/lib/action_view/base.rb +73 -390
  63. data/lib/action_view/helpers/active_record_helper.rb +83 -62
  64. data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
  65. data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
  66. data/lib/action_view/helpers/benchmark_helper.rb +5 -3
  67. data/lib/action_view/helpers/cache_helper.rb +3 -2
  68. data/lib/action_view/helpers/capture_helper.rb +1 -2
  69. data/lib/action_view/helpers/date_helper.rb +104 -82
  70. data/lib/action_view/helpers/form_helper.rb +148 -75
  71. data/lib/action_view/helpers/form_options_helper.rb +44 -23
  72. data/lib/action_view/helpers/form_tag_helper.rb +22 -13
  73. data/lib/action_view/helpers/javascripts/controls.js +1 -1
  74. data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
  75. data/lib/action_view/helpers/javascripts/effects.js +1 -1
  76. data/lib/action_view/helpers/number_helper.rb +10 -3
  77. data/lib/action_view/helpers/prototype_helper.rb +61 -29
  78. data/lib/action_view/helpers/record_tag_helper.rb +3 -3
  79. data/lib/action_view/helpers/sanitize_helper.rb +23 -17
  80. data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
  81. data/lib/action_view/helpers/text_helper.rb +153 -125
  82. data/lib/action_view/helpers/url_helper.rb +83 -28
  83. data/lib/action_view/inline_template.rb +20 -0
  84. data/lib/action_view/partial_template.rb +70 -0
  85. data/lib/action_view/partials.rb +31 -73
  86. data/lib/action_view/template.rb +127 -0
  87. data/lib/action_view/template_error.rb +8 -7
  88. data/lib/action_view/template_finder.rb +177 -0
  89. data/lib/action_view/template_handler.rb +18 -1
  90. data/lib/action_view/template_handlers/builder.rb +10 -2
  91. data/lib/action_view/template_handlers/compilable.rb +128 -0
  92. data/lib/action_view/template_handlers/erb.rb +37 -2
  93. data/lib/action_view/template_handlers/rjs.rb +14 -1
  94. data/lib/action_view/test_case.rb +58 -0
  95. data/test/abstract_unit.rb +1 -1
  96. data/test/active_record_unit.rb +3 -6
  97. data/test/activerecord/active_record_store_test.rb +1 -2
  98. data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
  99. data/test/adv_attr_test.rb +20 -0
  100. data/test/controller/action_pack_assertions_test.rb +16 -19
  101. data/test/controller/addresses_render_test.rb +1 -1
  102. data/test/controller/assert_select_test.rb +13 -6
  103. data/test/controller/base_test.rb +48 -2
  104. data/test/controller/benchmark_test.rb +1 -2
  105. data/test/controller/caching_test.rb +282 -21
  106. data/test/controller/capture_test.rb +1 -1
  107. data/test/controller/cgi_test.rb +1 -1
  108. data/test/controller/components_test.rb +1 -1
  109. data/test/controller/content_type_test.rb +2 -2
  110. data/test/controller/cookie_test.rb +13 -2
  111. data/test/controller/custom_handler_test.rb +14 -10
  112. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
  113. data/test/controller/dispatcher_test.rb +31 -49
  114. data/test/controller/fake_controllers.rb +17 -0
  115. data/test/controller/fake_models.rb +6 -0
  116. data/test/controller/filter_params_test.rb +14 -8
  117. data/test/controller/filters_test.rb +44 -16
  118. data/test/controller/flash_test.rb +2 -2
  119. data/test/controller/header_test.rb +14 -0
  120. data/test/controller/helper_test.rb +19 -15
  121. data/test/controller/html-scanner/document_test.rb +1 -2
  122. data/test/controller/html-scanner/node_test.rb +1 -2
  123. data/test/controller/html-scanner/sanitizer_test.rb +8 -5
  124. data/test/controller/html-scanner/tag_node_test.rb +1 -2
  125. data/test/controller/html-scanner/text_node_test.rb +2 -3
  126. data/test/controller/html-scanner/tokenizer_test.rb +8 -2
  127. data/test/controller/http_authentication_test.rb +1 -1
  128. data/test/controller/integration_test.rb +14 -16
  129. data/test/controller/integration_upload_test.rb +43 -0
  130. data/test/controller/layout_test.rb +26 -6
  131. data/test/controller/mime_responds_test.rb +39 -7
  132. data/test/controller/mime_type_test.rb +29 -5
  133. data/test/controller/new_render_test.rb +105 -34
  134. data/test/controller/polymorphic_routes_test.rb +32 -20
  135. data/test/controller/record_identifier_test.rb +38 -2
  136. data/test/controller/redirect_test.rb +21 -1
  137. data/test/controller/render_test.rb +59 -15
  138. data/test/controller/request_forgery_protection_test.rb +92 -5
  139. data/test/controller/request_test.rb +64 -6
  140. data/test/controller/rescue_test.rb +22 -6
  141. data/test/controller/resources_test.rb +102 -14
  142. data/test/controller/routing_test.rb +231 -19
  143. data/test/controller/selector_test.rb +2 -2
  144. data/test/controller/send_file_test.rb +14 -3
  145. data/test/controller/session/cookie_store_test.rb +16 -4
  146. data/test/controller/session/mem_cache_store_test.rb +3 -4
  147. data/test/controller/session_fixation_test.rb +1 -1
  148. data/test/controller/session_management_test.rb +23 -1
  149. data/test/controller/test_test.rb +39 -18
  150. data/test/controller/url_rewriter_test.rb +35 -1
  151. data/test/controller/verification_test.rb +1 -1
  152. data/test/controller/view_paths_test.rb +15 -12
  153. data/test/controller/webservice_test.rb +48 -3
  154. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  155. data/test/fixtures/company.rb +1 -0
  156. data/test/fixtures/customers/_customer.html.erb +1 -0
  157. data/test/fixtures/db_definitions/sqlite.sql +6 -0
  158. data/test/fixtures/functional_caching/_partial.erb +3 -0
  159. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  160. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  161. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  162. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  163. data/test/fixtures/mascot.rb +3 -0
  164. data/test/fixtures/mascots.yml +4 -0
  165. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  166. data/test/fixtures/multipart/boundary_problem_file +10 -0
  167. data/test/fixtures/public/javascripts/application.js +1 -0
  168. data/test/fixtures/public/javascripts/controls.js +1 -0
  169. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  170. data/test/fixtures/public/javascripts/effects.js +1 -0
  171. data/test/fixtures/public/javascripts/prototype.js +1 -0
  172. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  173. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  174. data/test/fixtures/reply.rb +1 -0
  175. data/test/fixtures/shared.html.erb +1 -0
  176. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  177. data/test/fixtures/test/_customer_counter.erb +1 -0
  178. data/test/fixtures/test/_form.erb +1 -0
  179. data/test/fixtures/test/_labelling_form.erb +1 -0
  180. data/test/fixtures/test/_raise.html.erb +1 -0
  181. data/test/fixtures/test/greeting.js.rjs +1 -0
  182. data/test/fixtures/topics/_topic.html.erb +1 -0
  183. data/test/template/active_record_helper_test.rb +25 -8
  184. data/test/template/asset_tag_helper_test.rb +100 -17
  185. data/test/template/atom_feed_helper_test.rb +29 -1
  186. data/test/template/benchmark_helper_test.rb +10 -22
  187. data/test/template/date_helper_test.rb +455 -153
  188. data/test/template/erb_util_test.rb +10 -42
  189. data/test/template/form_helper_test.rb +192 -66
  190. data/test/template/form_options_helper_test.rb +19 -8
  191. data/test/template/form_tag_helper_test.rb +11 -8
  192. data/test/template/javascript_helper_test.rb +3 -9
  193. data/test/template/number_helper_test.rb +6 -3
  194. data/test/template/prototype_helper_test.rb +27 -40
  195. data/test/template/record_tag_helper_test.rb +54 -0
  196. data/test/template/sanitize_helper_test.rb +5 -6
  197. data/test/template/scriptaculous_helper_test.rb +7 -13
  198. data/test/template/tag_helper_test.rb +3 -6
  199. data/test/template/template_finder_test.rb +73 -0
  200. data/test/template/template_object_test.rb +95 -0
  201. data/test/template/test_test.rb +56 -0
  202. data/test/template/text_helper_test.rb +46 -33
  203. data/test/template/url_helper_test.rb +8 -10
  204. metadata +65 -12
  205. data/lib/action_view/compiled_templates.rb +0 -69
  206. data/test/action_view_test.rb +0 -44
  207. data/test/activerecord/fixtures_test.rb +0 -24
  208. data/test/controller/fragment_store_setting_test.rb +0 -47
  209. data/test/template/compiled_templates_test.rb +0 -197
  210. data/test/template/deprecate_ivars_test.rb +0 -51
@@ -100,10 +100,10 @@ module ActionController #:nodoc:
100
100
  #
101
101
  # Around filters wrap an action, executing code both before and after.
102
102
  # They may be declared as method references, blocks, or objects responding
103
- # to #filter or to both #before and #after.
103
+ # to +filter+ or to both +before+ and +after+.
104
104
  #
105
- # To use a method as an around_filter, pass a symbol naming the Ruby method.
106
- # Yield (or block.call) within the method to run the action.
105
+ # To use a method as an +around_filter+, pass a symbol naming the Ruby method.
106
+ # Yield (or <tt>block.call</tt>) within the method to run the action.
107
107
  #
108
108
  # around_filter :catch_exceptions
109
109
  #
@@ -115,9 +115,9 @@ module ActionController #:nodoc:
115
115
  # raise
116
116
  # end
117
117
  #
118
- # To use a block as an around_filter, pass a block taking as args both
118
+ # To use a block as an +around_filter+, pass a block taking as args both
119
119
  # the controller and the action block. You can't call yield directly from
120
- # an around_filter block; explicitly call the action block instead:
120
+ # an +around_filter+ block; explicitly call the action block instead:
121
121
  #
122
122
  # around_filter do |controller, action|
123
123
  # logger.debug "before #{controller.action_name}"
@@ -125,9 +125,9 @@ module ActionController #:nodoc:
125
125
  # logger.debug "after #{controller.action_name}"
126
126
  # end
127
127
  #
128
- # To use a filter object with around_filter, pass an object responding
129
- # to :filter or both :before and :after. With a filter method, yield to
130
- # the block as above:
128
+ # To use a filter object with +around_filter+, pass an object responding
129
+ # to <tt>:filter</tt> or both <tt>:before</tt> and <tt>:after</tt>. With a
130
+ # filter method, yield to the block as above:
131
131
  #
132
132
  # around_filter BenchmarkingFilter
133
133
  #
@@ -137,7 +137,7 @@ module ActionController #:nodoc:
137
137
  # end
138
138
  # end
139
139
  #
140
- # With before and after methods:
140
+ # With +before+ and +after+ methods:
141
141
  #
142
142
  # around_filter Authorizer.new
143
143
  #
@@ -154,9 +154,9 @@ module ActionController #:nodoc:
154
154
  # end
155
155
  # end
156
156
  #
157
- # If the filter has before and after methods, the before method will be
158
- # called before the action. If before renders or redirects, the filter chain is
159
- # halted and after will not be run. See Filter Chain Halting below for
157
+ # If the filter has +before+ and +after+ methods, the +before+ method will be
158
+ # called before the action. If +before+ renders or redirects, the filter chain is
159
+ # halted and +after+ will not be run. See Filter Chain Halting below for
160
160
  # an example.
161
161
  #
162
162
  # == Filter chain skipping
@@ -191,8 +191,9 @@ module ActionController #:nodoc:
191
191
  # == Filter conditions
192
192
  #
193
193
  # Filters may be limited to specific actions by declaring the actions to
194
- # include or exclude. Both options accept single actions (:only => :index)
195
- # or arrays of actions (:except => [:foo, :bar]).
194
+ # include or exclude. Both options accept single actions
195
+ # (<tt>:only => :index</tt>) or arrays of actions
196
+ # (<tt>:except => [:foo, :bar]</tt>).
196
197
  #
197
198
  # class Journal < ActionController::Base
198
199
  # # Require authentication for edit and delete.
@@ -214,7 +215,7 @@ module ActionController #:nodoc:
214
215
  #
215
216
  # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
216
217
  # before a controller action is run. This is useful, for example, to deny
217
- # access to unauthenticated users or to redirect from http to https.
218
+ # access to unauthenticated users or to redirect from HTTP to HTTPS.
218
219
  # Simply call render or redirect. After filters will not be executed if the filter
219
220
  # chain is halted.
220
221
  #
@@ -240,21 +241,216 @@ module ActionController #:nodoc:
240
241
  # . /
241
242
  # #after (actual filter code is run, unless the around filter does not yield)
242
243
  #
243
- # If #around returns before yielding, #after will still not be run. The #before
244
- # filter and controller action will not be run. If #before renders or redirects,
245
- # the second half of #around and will still run but #after and the
246
- # action will not. If #around fails to yield, #after will not be run.
244
+ # If +around+ returns before yielding, +after+ will still not be run. The +before+
245
+ # filter and controller action will not be run. If +before+ renders or redirects,
246
+ # the second half of +around+ and will still run but +after+ and the
247
+ # action will not. If +around+ fails to yield, +after+ will not be run.
248
+
249
+ class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
250
+ def append_filter_to_chain(filters, filter_type, &block)
251
+ pos = find_filter_append_position(filters, filter_type)
252
+ update_filter_chain(filters, filter_type, pos, &block)
253
+ end
254
+
255
+ def prepend_filter_to_chain(filters, filter_type, &block)
256
+ pos = find_filter_prepend_position(filters, filter_type)
257
+ update_filter_chain(filters, filter_type, pos, &block)
258
+ end
259
+
260
+ def create_filters(filters, filter_type, &block)
261
+ filters, conditions = extract_options(filters, &block)
262
+ filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
263
+ filters
264
+ end
265
+
266
+ def skip_filter_in_chain(*filters, &test)
267
+ filters, conditions = extract_options(filters)
268
+ filters.each do |filter|
269
+ if callback = find(filter) then delete(callback) end
270
+ end if conditions.empty?
271
+ update_filter_in_chain(filters, :skip => conditions, &test)
272
+ end
273
+
274
+ private
275
+ def update_filter_chain(filters, filter_type, pos, &block)
276
+ new_filters = create_filters(filters, filter_type, &block)
277
+ insert(pos, new_filters).flatten!
278
+ end
279
+
280
+ def find_filter_append_position(filters, filter_type)
281
+ # appending an after filter puts it at the end of the call chain
282
+ # before and around filters go before the first after filter in the chain
283
+ unless filter_type == :after
284
+ each_with_index do |f,i|
285
+ return i if f.after?
286
+ end
287
+ end
288
+ return -1
289
+ end
290
+
291
+ def find_filter_prepend_position(filters, filter_type)
292
+ # prepending a before or around filter puts it at the front of the call chain
293
+ # after filters go before the first after filter in the chain
294
+ if filter_type == :after
295
+ each_with_index do |f,i|
296
+ return i if f.after?
297
+ end
298
+ return -1
299
+ end
300
+ return 0
301
+ end
302
+
303
+ def find_or_create_filter(filter, filter_type, options = {})
304
+ update_filter_in_chain([filter], options)
305
+
306
+ if found_filter = find(filter) { |f| f.type == filter_type }
307
+ found_filter
308
+ else
309
+ filter_kind = case
310
+ when filter.respond_to?(:before) && filter_type == :before
311
+ :before
312
+ when filter.respond_to?(:after) && filter_type == :after
313
+ :after
314
+ else
315
+ :filter
316
+ end
317
+
318
+ case filter_type
319
+ when :before
320
+ BeforeFilter.new(filter_kind, filter, options)
321
+ when :after
322
+ AfterFilter.new(filter_kind, filter, options)
323
+ else
324
+ AroundFilter.new(filter_kind, filter, options)
325
+ end
326
+ end
327
+ end
328
+
329
+ def update_filter_in_chain(filters, options, &test)
330
+ filters.map! { |f| block_given? ? find(f, &test) : find(f) }
331
+ filters.compact!
332
+
333
+ map! do |filter|
334
+ if filters.include?(filter)
335
+ new_filter = filter.dup
336
+ new_filter.options.merge!(options)
337
+ new_filter
338
+ else
339
+ filter
340
+ end
341
+ end
342
+ end
343
+ end
344
+
345
+ class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
346
+ def before?
347
+ self.class == BeforeFilter
348
+ end
349
+
350
+ def after?
351
+ self.class == AfterFilter
352
+ end
353
+
354
+ def around?
355
+ self.class == AroundFilter
356
+ end
357
+
358
+ private
359
+ def should_not_skip?(controller)
360
+ if options[:skip]
361
+ !included_in_action?(controller, options[:skip])
362
+ else
363
+ true
364
+ end
365
+ end
366
+
367
+ def included_in_action?(controller, options)
368
+ if options[:only]
369
+ Array(options[:only]).map(&:to_s).include?(controller.action_name)
370
+ elsif options[:except]
371
+ !Array(options[:except]).map(&:to_s).include?(controller.action_name)
372
+ else
373
+ true
374
+ end
375
+ end
376
+
377
+ def should_run_callback?(controller)
378
+ should_not_skip?(controller) && included_in_action?(controller, options) && super
379
+ end
380
+ end
381
+
382
+ class AroundFilter < Filter #:nodoc:
383
+ def type
384
+ :around
385
+ end
386
+
387
+ def call(controller, &block)
388
+ if should_run_callback?(controller)
389
+ method = filter_responds_to_before_and_after? ? around_proc : self.method
390
+
391
+ # For around_filter do |controller, action|
392
+ if method.is_a?(Proc) && method.arity == 2
393
+ evaluate_method(method, controller, block)
394
+ else
395
+ evaluate_method(method, controller, &block)
396
+ end
397
+ else
398
+ block.call
399
+ end
400
+ end
401
+
402
+ private
403
+ def filter_responds_to_before_and_after?
404
+ method.respond_to?(:before) && method.respond_to?(:after)
405
+ end
406
+
407
+ def around_proc
408
+ Proc.new do |controller, action|
409
+ method.before(controller)
410
+
411
+ if controller.send!(:performed?)
412
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
413
+ else
414
+ begin
415
+ action.call
416
+ ensure
417
+ method.after(controller)
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ class BeforeFilter < Filter #:nodoc:
425
+ def type
426
+ :before
427
+ end
428
+
429
+ def call(controller, &block)
430
+ super
431
+ if controller.send!(:performed?)
432
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
433
+ end
434
+ end
435
+ end
436
+
437
+ class AfterFilter < Filter #:nodoc:
438
+ def type
439
+ :after
440
+ end
441
+ end
442
+
247
443
  module ClassMethods
248
444
  # The passed <tt>filters</tt> will be appended to the filter_chain and
249
445
  # will execute before the action on this controller is performed.
250
446
  def append_before_filter(*filters, &block)
251
- append_filter_to_chain(filters, :before, &block)
447
+ filter_chain.append_filter_to_chain(filters, :before, &block)
252
448
  end
253
449
 
254
450
  # The passed <tt>filters</tt> will be prepended to the filter_chain and
255
451
  # will execute before the action on this controller is performed.
256
452
  def prepend_before_filter(*filters, &block)
257
- prepend_filter_to_chain(filters, :before, &block)
453
+ filter_chain.prepend_filter_to_chain(filters, :before, &block)
258
454
  end
259
455
 
260
456
  # Shorthand for append_before_filter since it's the most common.
@@ -263,20 +459,19 @@ module ActionController #:nodoc:
263
459
  # The passed <tt>filters</tt> will be appended to the array of filters
264
460
  # that run _after_ actions on this controller are performed.
265
461
  def append_after_filter(*filters, &block)
266
- append_filter_to_chain(filters, :after, &block)
462
+ filter_chain.append_filter_to_chain(filters, :after, &block)
267
463
  end
268
464
 
269
465
  # The passed <tt>filters</tt> will be prepended to the array of filters
270
466
  # that run _after_ actions on this controller are performed.
271
467
  def prepend_after_filter(*filters, &block)
272
- prepend_filter_to_chain(filters, :after, &block)
468
+ filter_chain.prepend_filter_to_chain(filters, :after, &block)
273
469
  end
274
470
 
275
471
  # Shorthand for append_after_filter since it's the most common.
276
472
  alias :after_filter :append_after_filter
277
473
 
278
-
279
- # If you append_around_filter A.new, B.new, the filter chain looks like
474
+ # If you <tt>append_around_filter A.new, B.new</tt>, the filter chain looks like
280
475
  #
281
476
  # B#before
282
477
  # A#before
@@ -284,16 +479,13 @@ module ActionController #:nodoc:
284
479
  # A#after
285
480
  # B#after
286
481
  #
287
- # With around filters which yield to the action block, #before and #after
482
+ # With around filters which yield to the action block, +before+ and +after+
288
483
  # are the code before and after the yield.
289
484
  def append_around_filter(*filters, &block)
290
- filters, conditions = extract_conditions(filters, &block)
291
- filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
292
- append_filter_to_chain([filter, conditions])
293
- end
485
+ filter_chain.append_filter_to_chain(filters, :around, &block)
294
486
  end
295
487
 
296
- # If you prepend_around_filter A.new, B.new, the filter chain looks like:
488
+ # If you <tt>prepend_around_filter A.new, B.new</tt>, the filter chain looks like:
297
489
  #
298
490
  # A#before
299
491
  # B#before
@@ -301,16 +493,13 @@ module ActionController #:nodoc:
301
493
  # B#after
302
494
  # A#after
303
495
  #
304
- # With around filters which yield to the action block, #before and #after
496
+ # With around filters which yield to the action block, +before+ and +after+
305
497
  # are the code before and after the yield.
306
498
  def prepend_around_filter(*filters, &block)
307
- filters, conditions = extract_conditions(filters, &block)
308
- filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
309
- prepend_filter_to_chain([filter, conditions])
310
- end
499
+ filter_chain.prepend_filter_to_chain(filters, :around, &block)
311
500
  end
312
501
 
313
- # Shorthand for append_around_filter since it's the most common.
502
+ # Shorthand for +append_around_filter+ since it's the most common.
314
503
  alias :around_filter :append_around_filter
315
504
 
316
505
  # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
@@ -320,7 +509,7 @@ module ActionController #:nodoc:
320
509
  # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
321
510
  # just like when you apply the filters.
322
511
  def skip_before_filter(*filters)
323
- skip_filter_in_chain(*filters, &:before?)
512
+ filter_chain.skip_filter_in_chain(*filters, &:before?)
324
513
  end
325
514
 
326
515
  # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
@@ -330,7 +519,7 @@ module ActionController #:nodoc:
330
519
  # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
331
520
  # just like when you apply the filters.
332
521
  def skip_after_filter(*filters)
333
- skip_filter_in_chain(*filters, &:after?)
522
+ filter_chain.skip_filter_in_chain(*filters, &:after?)
334
523
  end
335
524
 
336
525
  # Removes the specified filters from the filter chain. This only works for method reference (symbol)
@@ -340,336 +529,30 @@ module ActionController #:nodoc:
340
529
  # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
341
530
  # just like when you apply the filters.
342
531
  def skip_filter(*filters)
343
- skip_filter_in_chain(*filters)
532
+ filter_chain.skip_filter_in_chain(*filters)
344
533
  end
345
534
 
346
535
  # Returns an array of Filter objects for this controller.
347
536
  def filter_chain
348
- read_inheritable_attribute("filter_chain") || []
537
+ if chain = read_inheritable_attribute('filter_chain')
538
+ return chain
539
+ else
540
+ write_inheritable_attribute('filter_chain', FilterChain.new)
541
+ return filter_chain
542
+ end
349
543
  end
350
544
 
351
545
  # Returns all the before filters for this class and all its ancestors.
352
546
  # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
353
547
  def before_filters #:nodoc:
354
- filter_chain.select(&:before?).map(&:filter)
548
+ filter_chain.select(&:before?).map(&:method)
355
549
  end
356
550
 
357
551
  # Returns all the after filters for this class and all its ancestors.
358
552
  # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
359
553
  def after_filters #:nodoc:
360
- filter_chain.select(&:after?).map(&:filter)
361
- end
362
-
363
- # Returns a mapping between filters and the actions that may run them.
364
- def included_actions #:nodoc:
365
- @included_actions ||= read_inheritable_attribute("included_actions") || {}
366
- end
367
-
368
- # Returns a mapping between filters and actions that may not run them.
369
- def excluded_actions #:nodoc:
370
- @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
371
- end
372
-
373
- # Find a filter in the filter_chain where the filter method matches the _filter_ param
374
- # and (optionally) the passed block evaluates to true (mostly used for testing before?
375
- # and after? on the filter). Useful for symbol filters.
376
- #
377
- # The object of type Filter is passed to the block when yielded, not the filter itself.
378
- def find_filter(filter, &block) #:nodoc:
379
- filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
380
- end
381
-
382
- # Returns true if the filter is excluded from the given action
383
- def filter_excluded_from_action?(filter,action) #:nodoc:
384
- case
385
- when ia = included_actions[filter]
386
- !ia.include?(action)
387
- when ea = excluded_actions[filter]
388
- ea.include?(action)
389
- end
390
- end
391
-
392
- # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
393
- # contains no logic for calling the actual filters.
394
- class Filter #:nodoc:
395
- attr_reader :filter, :included_actions, :excluded_actions
396
-
397
- def initialize(filter)
398
- @filter = filter
399
- end
400
-
401
- def type
402
- :around
403
- end
404
-
405
- def before?
406
- type == :before
407
- end
408
-
409
- def after?
410
- type == :after
411
- end
412
-
413
- def around?
414
- type == :around
415
- end
416
-
417
- def run(controller)
418
- raise ActionControllerError, 'No filter type: Nothing to do here.'
419
- end
420
-
421
- def call(controller, &block)
422
- run(controller)
423
- end
424
- end
425
-
426
- # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
427
- # before_filter and after_filter by moving the logic into the filter itself.
428
- class FilterProxy < Filter #:nodoc:
429
- def filter
430
- @filter.filter
431
- end
432
- end
433
-
434
- class BeforeFilterProxy < FilterProxy #:nodoc:
435
- def type
436
- :before
437
- end
438
-
439
- def run(controller)
440
- # only filters returning false are halted.
441
- @filter.call(controller)
442
- if controller.send!(:performed?)
443
- controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected)
444
- end
445
- end
446
-
447
- def call(controller)
448
- yield unless run(controller)
449
- end
450
- end
451
-
452
- class AfterFilterProxy < FilterProxy #:nodoc:
453
- def type
454
- :after
455
- end
456
-
457
- def run(controller)
458
- @filter.call(controller)
459
- end
460
-
461
- def call(controller)
462
- yield
463
- run(controller)
464
- end
465
- end
466
-
467
- class SymbolFilter < Filter #:nodoc:
468
- def call(controller, &block)
469
- controller.send!(@filter, &block)
470
- end
471
- end
472
-
473
- class ProcFilter < Filter #:nodoc:
474
- def call(controller)
475
- @filter.call(controller)
476
- rescue LocalJumpError # a yield from a proc... no no bad dog.
477
- raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
478
- end
479
- end
480
-
481
- class ProcWithCallFilter < Filter #:nodoc:
482
- def call(controller, &block)
483
- @filter.call(controller, block)
484
- rescue LocalJumpError # a yield from a proc... no no bad dog.
485
- raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
486
- end
487
- end
488
-
489
- class MethodFilter < Filter #:nodoc:
490
- def call(controller, &block)
491
- @filter.call(controller, &block)
492
- end
493
- end
494
-
495
- class ClassFilter < Filter #:nodoc:
496
- def call(controller, &block)
497
- @filter.filter(controller, &block)
498
- end
499
- end
500
-
501
- class ClassBeforeFilter < Filter #:nodoc:
502
- def call(controller, &block)
503
- @filter.before(controller)
504
- end
554
+ filter_chain.select(&:after?).map(&:method)
505
555
  end
506
-
507
- class ClassAfterFilter < Filter #:nodoc:
508
- def call(controller, &block)
509
- @filter.after(controller)
510
- end
511
- end
512
-
513
- protected
514
- def append_filter_to_chain(filters, filter_type = :around, &block)
515
- pos = find_filter_append_position(filters, filter_type)
516
- update_filter_chain(filters, filter_type, pos, &block)
517
- end
518
-
519
- def prepend_filter_to_chain(filters, filter_type = :around, &block)
520
- pos = find_filter_prepend_position(filters, filter_type)
521
- update_filter_chain(filters, filter_type, pos, &block)
522
- end
523
-
524
- def update_filter_chain(filters, filter_type, pos, &block)
525
- new_filters = create_filters(filters, filter_type, &block)
526
- new_chain = filter_chain.insert(pos, new_filters).flatten
527
- write_inheritable_attribute('filter_chain', new_chain)
528
- end
529
-
530
- def find_filter_append_position(filters, filter_type)
531
- # appending an after filter puts it at the end of the call chain
532
- # before and around filters go before the first after filter in the chain
533
- unless filter_type == :after
534
- filter_chain.each_with_index do |f,i|
535
- return i if f.after?
536
- end
537
- end
538
- return -1
539
- end
540
-
541
- def find_filter_prepend_position(filters, filter_type)
542
- # prepending a before or around filter puts it at the front of the call chain
543
- # after filters go before the first after filter in the chain
544
- if filter_type == :after
545
- filter_chain.each_with_index do |f,i|
546
- return i if f.after?
547
- end
548
- return -1
549
- end
550
- return 0
551
- end
552
-
553
- def create_filters(filters, filter_type, &block) #:nodoc:
554
- filters, conditions = extract_conditions(filters, &block)
555
- filters.map! { |filter| find_or_create_filter(filter, filter_type) }
556
- update_conditions(filters, conditions)
557
- filters
558
- end
559
-
560
- def find_or_create_filter(filter, filter_type)
561
- if found_filter = find_filter(filter) { |f| f.type == filter_type }
562
- found_filter
563
- else
564
- f = class_for_filter(filter, filter_type).new(filter)
565
- # apply proxy to filter if necessary
566
- case filter_type
567
- when :before
568
- BeforeFilterProxy.new(f)
569
- when :after
570
- AfterFilterProxy.new(f)
571
- else
572
- f
573
- end
574
- end
575
- end
576
-
577
- # The determination of the filter type was once done at run time.
578
- # This method is here to extract as much logic from the filter run time as possible
579
- def class_for_filter(filter, filter_type) #:nodoc:
580
- case
581
- when filter.is_a?(Symbol)
582
- SymbolFilter
583
- when filter.respond_to?(:call)
584
- if filter.is_a?(Method)
585
- MethodFilter
586
- else
587
- case filter.arity
588
- when 1; ProcFilter
589
- when 2; ProcWithCallFilter
590
- else raise ArgumentError, 'Filter blocks must take one or two arguments.'
591
- end
592
- end
593
- when filter.respond_to?(:filter)
594
- ClassFilter
595
- when filter.respond_to?(:before) && filter_type == :before
596
- ClassBeforeFilter
597
- when filter.respond_to?(:after) && filter_type == :after
598
- ClassAfterFilter
599
- else
600
- raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')
601
- end
602
- end
603
-
604
- def extract_conditions(*filters, &block) #:nodoc:
605
- filters.flatten!
606
- conditions = filters.extract_options!
607
- filters << block if block_given?
608
- return filters, conditions
609
- end
610
-
611
- def update_conditions(filters, conditions)
612
- return if conditions.empty?
613
- if conditions[:only]
614
- write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
615
- elsif conditions[:except]
616
- write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except]))
617
- end
618
- end
619
-
620
- def condition_hash(filters, *actions)
621
- actions = actions.flatten.map(&:to_s)
622
- filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
623
- end
624
-
625
- def skip_filter_in_chain(*filters, &test) #:nodoc:
626
- filters, conditions = extract_conditions(filters)
627
- filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
628
- filters.compact!
629
-
630
- if conditions.empty?
631
- delete_filters_in_chain(filters)
632
- else
633
- remove_actions_from_included_actions!(filters,conditions[:only] || [])
634
- conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
635
- update_conditions(filters,conditions)
636
- end
637
- end
638
-
639
- def remove_actions_from_included_actions!(filters,*actions)
640
- actions = actions.flatten.map(&:to_s)
641
- updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter|
642
- ia = (hash[filter] || []) - actions
643
- ia.empty? ? hash.delete(filter) : hash[filter] = ia
644
- hash
645
- end
646
- write_inheritable_attribute('included_actions', updated_hash)
647
- end
648
-
649
- def delete_filters_in_chain(filters) #:nodoc:
650
- write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
651
- end
652
-
653
- def filter_responds_to_before_and_after(filter) #:nodoc:
654
- filter.respond_to?(:before) && filter.respond_to?(:after)
655
- end
656
-
657
- def proxy_before_and_after_filter(filter) #:nodoc:
658
- return filter unless filter_responds_to_before_and_after(filter)
659
- Proc.new do |controller, action|
660
- filter.before(controller)
661
-
662
- if controller.send!(:performed?)
663
- controller.send!(:halt_filter_chain, filter, :rendered_or_redirected)
664
- else
665
- begin
666
- action.call
667
- ensure
668
- filter.after(controller)
669
- end
670
- end
671
- end
672
- end
673
556
  end
674
557
 
675
558
  module InstanceMethods # :nodoc:
@@ -681,89 +564,80 @@ module ActionController #:nodoc:
681
564
  end
682
565
 
683
566
  protected
567
+ def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
568
+ @before_filter_chain_aborted = false
569
+ process_without_filters(request, response, method, *arguments)
570
+ end
684
571
 
685
- def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
686
- @before_filter_chain_aborted = false
687
- process_without_filters(request, response, method, *arguments)
688
- end
689
-
690
- def perform_action_with_filters
691
- call_filters(self.class.filter_chain, 0, 0)
692
- end
572
+ def perform_action_with_filters
573
+ call_filters(self.class.filter_chain, 0, 0)
574
+ end
693
575
 
694
576
  private
577
+ def call_filters(chain, index, nesting)
578
+ index = run_before_filters(chain, index, nesting)
579
+ aborted = @before_filter_chain_aborted
580
+ perform_action_without_filters unless performed? || aborted
581
+ return index if nesting != 0 || aborted
582
+ run_after_filters(chain, index)
583
+ end
584
+
585
+ def run_before_filters(chain, index, nesting)
586
+ while chain[index]
587
+ filter, index = chain[index], index
588
+ break unless filter # end of call chain reached
589
+
590
+ case filter
591
+ when BeforeFilter
592
+ filter.call(self) # invoke before filter
593
+ index = index.next
594
+ break if @before_filter_chain_aborted
595
+ when AroundFilter
596
+ yielded = false
597
+
598
+ filter.call(self) do
599
+ yielded = true
600
+ # all remaining before and around filters will be run in this call
601
+ index = call_filters(chain, index.next, nesting.next)
602
+ end
695
603
 
696
- def call_filters(chain, index, nesting)
697
- index = run_before_filters(chain, index, nesting)
698
- aborted = @before_filter_chain_aborted
699
- perform_action_without_filters unless performed? || aborted
700
- return index if nesting != 0 || aborted
701
- run_after_filters(chain, index)
702
- end
703
-
704
- def skip_excluded_filters(chain, index)
705
- while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name)
706
- index = index.next
707
- end
708
- [filter, index]
709
- end
710
-
711
- def run_before_filters(chain, index, nesting)
712
- while chain[index]
713
- filter, index = skip_excluded_filters(chain, index)
714
- break unless filter # end of call chain reached
604
+ halt_filter_chain(filter, :did_not_yield) unless yielded
715
605
 
716
- case filter.type
717
- when :before
718
- filter.run(self) # invoke before filter
719
- index = index.next
720
- break if @before_filter_chain_aborted
721
- when :around
722
- yielded = false
723
-
724
- filter.call(self) do
725
- yielded = true
726
- # all remaining before and around filters will be run in this call
727
- index = call_filters(chain, index.next, nesting.next)
606
+ break
607
+ else
608
+ break # no before or around filters left
728
609
  end
729
-
730
- halt_filter_chain(filter, :did_not_yield) unless yielded
731
-
732
- break
733
- else
734
- break # no before or around filters left
735
610
  end
611
+
612
+ index
736
613
  end
737
614
 
738
- index
739
- end
615
+ def run_after_filters(chain, index)
616
+ seen_after_filter = false
740
617
 
741
- def run_after_filters(chain, index)
742
- seen_after_filter = false
618
+ while chain[index]
619
+ filter, index = chain[index], index
620
+ break unless filter # end of call chain reached
743
621
 
744
- while chain[index]
745
- filter, index = skip_excluded_filters(chain, index)
746
- break unless filter # end of call chain reached
622
+ case filter
623
+ when AfterFilter
624
+ seen_after_filter = true
625
+ filter.call(self) # invoke after filter
626
+ else
627
+ # implementation error or someone has mucked with the filter chain
628
+ raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
629
+ end
747
630
 
748
- case filter.type
749
- when :after
750
- seen_after_filter = true
751
- filter.run(self) # invoke after filter
752
- else
753
- # implementation error or someone has mucked with the filter chain
754
- raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter
631
+ index = index.next
755
632
  end
756
633
 
757
- index = index.next
634
+ index.next
758
635
  end
759
636
 
760
- index.next
761
- end
762
-
763
- def halt_filter_chain(filter, reason)
764
- @before_filter_chain_aborted = true
765
- logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
766
- end
637
+ def halt_filter_chain(filter, reason)
638
+ @before_filter_chain_aborted = true
639
+ logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger
640
+ end
767
641
  end
768
642
  end
769
643
  end