actionpack 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/CHANGELOG +517 -15
  2. data/MIT-LICENSE +1 -1
  3. data/README +18 -20
  4. data/Rakefile +7 -4
  5. data/examples/address_book_controller.rb +3 -3
  6. data/examples/blog_controller.cgi +3 -3
  7. data/examples/debate_controller.cgi +5 -5
  8. data/lib/action_controller.rb +2 -2
  9. data/lib/action_controller/assertions.rb +73 -311
  10. data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
  11. data/lib/action_controller/assertions/dom_assertions.rb +25 -0
  12. data/lib/action_controller/assertions/model_assertions.rb +12 -0
  13. data/lib/action_controller/assertions/response_assertions.rb +140 -0
  14. data/lib/action_controller/assertions/routing_assertions.rb +82 -0
  15. data/lib/action_controller/assertions/selector_assertions.rb +571 -0
  16. data/lib/action_controller/assertions/tag_assertions.rb +117 -0
  17. data/lib/action_controller/base.rb +334 -163
  18. data/lib/action_controller/benchmarking.rb +3 -6
  19. data/lib/action_controller/caching.rb +83 -22
  20. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
  21. data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
  22. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
  23. data/lib/action_controller/cgi_process.rb +50 -27
  24. data/lib/action_controller/components.rb +21 -25
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
  27. data/lib/action_controller/filters.rb +448 -225
  28. data/lib/action_controller/flash.rb +24 -20
  29. data/lib/action_controller/helpers.rb +2 -5
  30. data/lib/action_controller/integration.rb +40 -16
  31. data/lib/action_controller/layout.rb +11 -8
  32. data/lib/action_controller/macros/auto_complete.rb +3 -2
  33. data/lib/action_controller/macros/in_place_editing.rb +3 -2
  34. data/lib/action_controller/mime_responds.rb +41 -29
  35. data/lib/action_controller/mime_type.rb +68 -10
  36. data/lib/action_controller/pagination.rb +4 -3
  37. data/lib/action_controller/request.rb +22 -14
  38. data/lib/action_controller/rescue.rb +25 -22
  39. data/lib/action_controller/resources.rb +302 -0
  40. data/lib/action_controller/response.rb +20 -2
  41. data/lib/action_controller/response.rb.rej +17 -0
  42. data/lib/action_controller/routing.rb +1165 -567
  43. data/lib/action_controller/scaffolding.rb +30 -31
  44. data/lib/action_controller/session/active_record_store.rb +2 -0
  45. data/lib/action_controller/session/drb_store.rb +4 -0
  46. data/lib/action_controller/session/mem_cache_store.rb +4 -0
  47. data/lib/action_controller/session_management.rb +6 -9
  48. data/lib/action_controller/status_codes.rb +89 -0
  49. data/lib/action_controller/streaming.rb +6 -15
  50. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
  51. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
  52. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
  53. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
  54. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  55. data/lib/action_controller/test_process.rb +52 -30
  56. data/lib/action_controller/url_rewriter.rb +63 -29
  57. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
  58. data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
  59. data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
  60. data/lib/action_controller/verification.rb +22 -11
  61. data/lib/action_pack.rb +1 -1
  62. data/lib/action_pack/version.rb +2 -2
  63. data/lib/action_view.rb +1 -1
  64. data/lib/action_view/base.rb +46 -43
  65. data/lib/action_view/compiled_templates.rb +1 -1
  66. data/lib/action_view/helpers/active_record_helper.rb +54 -17
  67. data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
  68. data/lib/action_view/helpers/capture_helper.rb +1 -1
  69. data/lib/action_view/helpers/date_helper.rb +258 -136
  70. data/lib/action_view/helpers/debug_helper.rb +1 -1
  71. data/lib/action_view/helpers/deprecated_helper.rb +34 -0
  72. data/lib/action_view/helpers/form_helper.rb +75 -35
  73. data/lib/action_view/helpers/form_options_helper.rb +7 -5
  74. data/lib/action_view/helpers/form_tag_helper.rb +44 -6
  75. data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
  76. data/lib/action_view/helpers/javascript_helper.rb +71 -10
  77. data/lib/action_view/helpers/javascripts/controls.js +41 -23
  78. data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
  79. data/lib/action_view/helpers/javascripts/effects.js +293 -163
  80. data/lib/action_view/helpers/javascripts/prototype.js +897 -389
  81. data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
  82. data/lib/action_view/helpers/number_helper.rb +111 -65
  83. data/lib/action_view/helpers/prototype_helper.rb +84 -109
  84. data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
  85. data/lib/action_view/helpers/tag_helper.rb +69 -16
  86. data/lib/action_view/helpers/text_helper.rb +149 -112
  87. data/lib/action_view/helpers/url_helper.rb +200 -107
  88. data/lib/action_view/template_error.rb +66 -42
  89. data/test/abstract_unit.rb +4 -2
  90. data/test/active_record_unit.rb +84 -56
  91. data/test/activerecord/active_record_assertions_test.rb +26 -18
  92. data/test/activerecord/active_record_store_test.rb +4 -36
  93. data/test/activerecord/pagination_test.rb +1 -6
  94. data/test/controller/action_pack_assertions_test.rb +230 -113
  95. data/test/controller/addresses_render_test.rb +2 -6
  96. data/test/controller/assert_select_test.rb +576 -0
  97. data/test/controller/base_test.rb +73 -3
  98. data/test/controller/caching_test.rb +228 -0
  99. data/test/controller/capture_test.rb +12 -10
  100. data/test/controller/cgi_test.rb +89 -12
  101. data/test/controller/components_test.rb +24 -2
  102. data/test/controller/content_type_test.rb +139 -0
  103. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  104. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  105. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  106. data/test/controller/cookie_test.rb +33 -25
  107. data/test/controller/deprecated_instance_variables_test.rb +48 -0
  108. data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
  109. data/test/controller/fake_controllers.rb +0 -1
  110. data/test/controller/filters_test.rb +301 -16
  111. data/test/controller/flash_test.rb +19 -2
  112. data/test/controller/helper_test.rb +2 -2
  113. data/test/controller/integration_test.rb +154 -0
  114. data/test/controller/layout_test.rb +115 -1
  115. data/test/controller/mime_responds_test.rb +94 -0
  116. data/test/controller/mime_type_test.rb +9 -0
  117. data/test/controller/new_render_test.rb +161 -11
  118. data/test/controller/raw_post_test.rb +52 -15
  119. data/test/controller/redirect_test.rb +27 -14
  120. data/test/controller/render_test.rb +76 -29
  121. data/test/controller/request_test.rb +55 -4
  122. data/test/controller/resources_test.rb +274 -0
  123. data/test/controller/routing_test.rb +1533 -824
  124. data/test/controller/selector_test.rb +628 -0
  125. data/test/controller/send_file_test.rb +9 -1
  126. data/test/controller/session_management_test.rb +51 -0
  127. data/test/controller/test_test.rb +113 -29
  128. data/test/controller/url_rewriter_test.rb +86 -17
  129. data/test/controller/verification_test.rb +19 -17
  130. data/test/controller/webservice_test.rb +0 -7
  131. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  132. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  133. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  134. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  135. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
  136. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
  137. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
  138. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
  139. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
  140. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
  141. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
  142. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
  143. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
  144. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
  145. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
  146. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
  147. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
  148. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
  149. data/test/fixtures/multipart/binary_file +0 -0
  150. data/test/fixtures/public/javascripts/application.js +1 -0
  151. data/test/fixtures/test/_hello.rxml +1 -0
  152. data/test/fixtures/test/hello_world_container.rxml +3 -0
  153. data/test/fixtures/topic.rb +2 -2
  154. data/test/template/active_record_helper_test.rb +83 -12
  155. data/test/template/asset_tag_helper_test.rb +75 -95
  156. data/test/template/compiled_templates_test.rb +1 -0
  157. data/test/template/date_helper_test.rb +873 -181
  158. data/test/template/deprecated_helper_test.rb +36 -0
  159. data/test/template/deprecated_instance_variables_test.rb +43 -0
  160. data/test/template/form_helper_test.rb +77 -1
  161. data/test/template/form_options_helper_test.rb +4 -0
  162. data/test/template/form_tag_helper_test.rb +66 -2
  163. data/test/template/java_script_macros_helper_test.rb +4 -1
  164. data/test/template/javascript_helper_test.rb +29 -0
  165. data/test/template/number_helper_test.rb +63 -27
  166. data/test/template/prototype_helper_test.rb +77 -34
  167. data/test/template/tag_helper_test.rb +34 -6
  168. data/test/template/text_helper_test.rb +69 -34
  169. data/test/template/url_helper_test.rb +168 -16
  170. data/test/testing_sandbox.rb +7 -22
  171. metadata +66 -20
  172. data/filler.txt +0 -50
  173. data/lib/action_controller/code_generation.rb +0 -235
  174. data/lib/action_controller/vendor/xml_simple.rb +0 -1019
  175. data/test/controller/caching_filestore.rb +0 -74
  176. data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
  177. data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
  178. data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
  179. data/test/fixtures/dont_load.rb +0 -3
@@ -5,19 +5,14 @@ module ActionController #:nodoc:
5
5
  base.send(:include, ActionController::Filters::InstanceMethods)
6
6
  end
7
7
 
8
- # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
9
- # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
10
- # compression after the action has been performed.
11
- #
12
- # Filters have access to the request, response, and all the instance variables set by other filters in the chain
13
- # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
14
- # to halt the processing before the intended action is processed by returning false or performing a redirect or render.
15
- # This is especially useful for filters like authentication where you're not interested in allowing the action to be
16
- # performed if the proper credentials are not in order.
8
+ # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
9
+ # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
10
+ # compression after the action has been performed. Filters have access to the request, response, and all the instance
11
+ # variables set by other filters in the chain or by the action (in the case of after filters).
17
12
  #
18
13
  # == Filter inheritance
19
14
  #
20
- # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
15
+ # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without
21
16
  # affecting the superclass. For example:
22
17
  #
23
18
  # class BankController < ActionController::Base
@@ -39,7 +34,7 @@ module ActionController #:nodoc:
39
34
  # end
40
35
  #
41
36
  # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
42
- # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
37
+ # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
43
38
  # verify_credentials and the intended action are never called.
44
39
  #
45
40
  # == Filter types
@@ -64,7 +59,7 @@ module ActionController #:nodoc:
64
59
  # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
65
60
  # manipulate them as it sees fit.
66
61
  #
67
- # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
62
+ # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
68
63
  # Or just as a quick test. It works like this:
69
64
  #
70
65
  # class WeblogController < ActionController::Base
@@ -76,6 +71,9 @@ module ActionController #:nodoc:
76
71
  # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
77
72
  # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
78
73
  #
74
+ # Please note that around_filters function a little differently than the normal before and after filters with regard to filter
75
+ # types. Please see the section dedicated to around_filters below.
76
+ #
79
77
  # == Filter chain ordering
80
78
  #
81
79
  # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
@@ -83,14 +81,14 @@ module ActionController #:nodoc:
83
81
  # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
84
82
  # beginning of their respective chain and executed before the rest. For example:
85
83
  #
86
- # class ShoppingController
84
+ # class ShoppingController < ActionController::Base
87
85
  # before_filter :verify_open_shop
88
86
  #
89
- # class CheckoutController
87
+ # class CheckoutController < ShoppingController
90
88
  # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
91
89
  #
92
90
  # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
93
- # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
91
+ # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
94
92
  # is open or not.
95
93
  #
96
94
  # You may pass multiple filter arguments of each type as well as a filter block.
@@ -98,250 +96,511 @@ module ActionController #:nodoc:
98
96
  #
99
97
  # == Around filters
100
98
  #
101
- # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
102
- # both the before and after call. That's especially useful when you need to keep state active between the before and after,
103
- # such as the example of a benchmark filter below:
104
- #
105
- # class WeblogController < ActionController::Base
106
- # around_filter BenchmarkingFilter.new
107
- #
108
- # # Before this action is performed, BenchmarkingFilter#before(controller) is executed
109
- # def index
99
+ # Around filters wrap an action, executing code both before and after.
100
+ # They may be declared as method references, blocks, or objects responding
101
+ # to #filter or to both #before and #after.
102
+ #
103
+ # To use a method as an around_filter, pass a symbol naming the Ruby method.
104
+ # Yield (or block.call) within the method to run the action.
105
+ #
106
+ # around_filter :catch_exceptions
107
+ #
108
+ # private
109
+ # def catch_exceptions
110
+ # yield
111
+ # rescue => exception
112
+ # logger.debug "Caught exception! #{exception}"
113
+ # raise
110
114
  # end
111
- # # After this action has been performed, BenchmarkingFilter#after(controller) is executed
115
+ #
116
+ # To use a block as an around_filter, pass a block taking as args both
117
+ # the controller and the action block. You can't call yield directly from
118
+ # an around_filter block; explicitly call the action block instead:
119
+ #
120
+ # around_filter do |controller, action|
121
+ # logger.debug "before #{controller.action_name}"
122
+ # action.call
123
+ # logger.debug "after #{controller.action_name}"
112
124
  # end
113
125
  #
126
+ # To use a filter object with around_filter, pass an object responding
127
+ # to :filter or both :before and :after. With a filter method, yield to
128
+ # the block as above:
129
+ #
130
+ # around_filter BenchmarkingFilter
131
+ #
114
132
  # class BenchmarkingFilter
115
- # def initialize
116
- # @runtime
133
+ # def self.filter(controller, &block)
134
+ # Benchmark.measure(&block)
117
135
  # end
118
- #
119
- # def before
120
- # start_timer
136
+ # end
137
+ #
138
+ # With before and after methods:
139
+ #
140
+ # around_filter Authorizer.new
141
+ #
142
+ # class Authorizer
143
+ # # This will run before the action. Returning false aborts the action.
144
+ # def before(controller)
145
+ # if user.authorized?
146
+ # return true
147
+ # else
148
+ # redirect_to login_url
149
+ # return false
150
+ # end
121
151
  # end
122
- #
123
- # def after
124
- # stop_timer
125
- # report_result
152
+ #
153
+ # # This will run after the action if and only if before returned true.
154
+ # def after(controller)
126
155
  # end
127
156
  # end
128
157
  #
158
+ # If the filter has before and after methods, the before method will be
159
+ # called before the action. If before returns false, the filter chain is
160
+ # halted and after will not be run. See Filter Chain Halting below for
161
+ # an example.
162
+ #
129
163
  # == Filter chain skipping
130
164
  #
131
- # Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
132
- # subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
133
- # they would like to be relieved of. Examples
165
+ # Declaring a filter on a base class conveniently applies to its subclasses,
166
+ # but sometimes a subclass should skip some of its superclass' filters:
134
167
  #
135
168
  # class ApplicationController < ActionController::Base
136
169
  # before_filter :authenticate
170
+ # around_filter :catch_exceptions
137
171
  # end
138
172
  #
139
173
  # class WeblogController < ApplicationController
140
- # # will run the :authenticate filter
174
+ # # Will run the :authenticate and :catch_exceptions filters.
141
175
  # end
142
176
  #
143
177
  # class SignupController < ApplicationController
144
- # # will not run the :authenticate filter
178
+ # # Skip :authenticate, run :catch_exceptions.
145
179
  # skip_before_filter :authenticate
146
180
  # end
147
181
  #
182
+ # class ProjectsController < ApplicationController
183
+ # # Skip :catch_exceptions, run :authenticate.
184
+ # skip_filter :catch_exceptions
185
+ # end
186
+ #
187
+ # class ClientsController < ApplicationController
188
+ # # Skip :catch_exceptions and :authenticate unless action is index.
189
+ # skip_filter :catch_exceptions, :authenticate, :except => :index
190
+ # end
191
+ #
148
192
  # == Filter conditions
149
193
  #
150
- # Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
151
- # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
152
- # of which accept an arbitrary number of method references. For example:
194
+ # Filters may be limited to specific actions by declaring the actions to
195
+ # include or exclude. Both options accept single actions (:only => :index)
196
+ # or arrays of actions (:except => [:foo, :bar]).
153
197
  #
154
198
  # class Journal < ActionController::Base
155
- # # only require authentication if the current action is edit or delete
156
- # before_filter :authorize, :only => [ :edit, :delete ]
157
- #
199
+ # # Require authentication for edit and delete.
200
+ # before_filter :authorize, :only => [:edit, :delete]
201
+ #
202
+ # # Passing options to a filter with a block.
203
+ # around_filter(:except => :index) do |controller, action_block|
204
+ # results = Profiler.run(&action_block)
205
+ # controller.response.sub! "</body>", "#{results}</body>"
206
+ # end
207
+ #
158
208
  # private
159
209
  # def authorize
160
- # # redirect to login unless authenticated
210
+ # # Redirect to login unless authenticated.
161
211
  # end
162
212
  # end
163
- #
164
- # When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
165
- #
166
- # class UserPreferences < ActionController::Base
167
- # before_filter(:except => :new) { # some proc ... }
168
- # # ...
169
- # end
170
213
  #
214
+ # == Filter Chain Halting
215
+ #
216
+ # <tt>before_filter</tt> and <tt>around_filter</tt> may halt the request
217
+ # before controller action is run. This is useful, for example, to deny
218
+ # access to unauthenticated users or to redirect from http to https.
219
+ # Simply return false from the filter or call render or redirect.
220
+ #
221
+ # Around filters halt the request unless the action block is called.
222
+ # Given these filters
223
+ # after_filter :after
224
+ # around_filter :around
225
+ # before_filter :before
226
+ #
227
+ # The filter chain will look like:
228
+ #
229
+ # ...
230
+ # . \
231
+ # . #around (code before yield)
232
+ # . . \
233
+ # . . #before (actual filter code is run)
234
+ # . . . \
235
+ # . . . execute controller action
236
+ # . . . /
237
+ # . . ...
238
+ # . . /
239
+ # . #around (code after yield)
240
+ # . /
241
+ # #after (actual filter code is run)
242
+ #
243
+ # If #around returns before yielding, only #after will be run. The #before
244
+ # filter and controller action will not be run. If #before returns false,
245
+ # the second half of #around and all of #after will still run but the
246
+ # action will not.
171
247
  module ClassMethods
172
- # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
173
- # on this controller are performed.
248
+ # The passed <tt>filters</tt> will be appended to the filter_chain and
249
+ # will execute before the action on this controller is performed.
174
250
  def append_before_filter(*filters, &block)
175
- conditions = extract_conditions!(filters)
176
- filters << block if block_given?
177
- add_action_conditions(filters, conditions)
178
- append_filter_to_chain('before', filters)
251
+ append_filter_to_chain(filters, :before, &block)
179
252
  end
180
253
 
181
- # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
182
- # on this controller are performed.
254
+ # The passed <tt>filters</tt> will be prepended to the filter_chain and
255
+ # will execute before the action on this controller is performed.
183
256
  def prepend_before_filter(*filters, &block)
184
- conditions = extract_conditions!(filters)
185
- filters << block if block_given?
186
- add_action_conditions(filters, conditions)
187
- prepend_filter_to_chain('before', filters)
257
+ prepend_filter_to_chain(filters, :before, &block)
188
258
  end
189
259
 
190
- # Short-hand for append_before_filter since that's the most common of the two.
260
+ # Shorthand for append_before_filter since it's the most common.
191
261
  alias :before_filter :append_before_filter
192
-
193
- # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
194
- # on this controller are performed.
262
+
263
+ # The passed <tt>filters</tt> will be appended to the array of filters
264
+ # that run _after_ actions on this controller are performed.
195
265
  def append_after_filter(*filters, &block)
196
- conditions = extract_conditions!(filters)
197
- filters << block if block_given?
198
- add_action_conditions(filters, conditions)
199
- append_filter_to_chain('after', filters)
266
+ prepend_filter_to_chain(filters, :after, &block)
200
267
  end
201
268
 
202
- # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
203
- # on this controller are performed.
269
+ # The passed <tt>filters</tt> will be prepended to the array of filters
270
+ # that run _after_ actions on this controller are performed.
204
271
  def prepend_after_filter(*filters, &block)
205
- conditions = extract_conditions!(filters)
206
- filters << block if block_given?
207
- add_action_conditions(filters, conditions)
208
- prepend_filter_to_chain("after", filters)
272
+ append_filter_to_chain(filters, :after, &block)
209
273
  end
210
274
 
211
- # Short-hand for append_after_filter since that's the most common of the two.
275
+ # Shorthand for append_after_filter since it's the most common.
212
276
  alias :after_filter :append_after_filter
213
-
214
- # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
215
- # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
216
- # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
277
+
278
+
279
+ # If you append_around_filter A.new, B.new, the filter chain looks like
217
280
  #
218
281
  # B#before
219
282
  # A#before
283
+ # # run the action
220
284
  # A#after
221
285
  # B#after
222
- def append_around_filter(*filters)
223
- conditions = extract_conditions!(filters)
224
- for filter in filters.flatten
225
- ensure_filter_responds_to_before_and_after(filter)
226
- append_before_filter(conditions || {}) { |c| filter.before(c) }
227
- prepend_after_filter(conditions || {}) { |c| filter.after(c) }
228
- end
229
- end
230
-
231
- # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
232
- # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
233
- # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
286
+ #
287
+ # With around filters which yield to the action block, #before and #after
288
+ # are the code before and after the yield.
289
+ 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
294
+ end
295
+
296
+ # If you prepend_around_filter A.new, B.new, the filter chain looks like:
234
297
  #
235
298
  # A#before
236
299
  # B#before
300
+ # # run the action
237
301
  # B#after
238
302
  # A#after
239
- def prepend_around_filter(*filters)
240
- for filter in filters.flatten
241
- ensure_filter_responds_to_before_and_after(filter)
242
- prepend_before_filter { |c| filter.before(c) }
243
- append_after_filter { |c| filter.after(c) }
303
+ #
304
+ # With around filters which yield to the action block, #before and #after
305
+ # are the code before and after the yield.
306
+ 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])
244
310
  end
245
- end
311
+ end
246
312
 
247
- # Short-hand for append_around_filter since that's the most common of the two.
313
+ # Shorthand for append_around_filter since it's the most common.
248
314
  alias :around_filter :append_around_filter
249
-
250
- # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
315
+
316
+ # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
251
317
  # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
252
318
  # of many sub-controllers need a different hierarchy.
253
319
  #
254
- # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
320
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
255
321
  # just like when you apply the filters.
256
322
  def skip_before_filter(*filters)
257
- if conditions = extract_conditions!(filters)
258
- remove_contradicting_conditions!(filters, conditions)
259
- conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
260
- add_action_conditions(filters, conditions)
261
- else
262
- for filter in filters.flatten
263
- write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
264
- end
265
- end
323
+ skip_filter_in_chain(*filters, &:before?)
266
324
  end
267
325
 
268
- # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
326
+ # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
269
327
  # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
270
328
  # of many sub-controllers need a different hierarchy.
271
329
  #
272
- # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
330
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
273
331
  # just like when you apply the filters.
274
332
  def skip_after_filter(*filters)
275
- if conditions = extract_conditions!(filters)
276
- remove_contradicting_conditions!(filters, conditions)
277
- conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
278
- add_action_conditions(filters, conditions)
279
- else
280
- for filter in filters.flatten
281
- write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
282
- end
283
- end
333
+ skip_filter_in_chain(*filters, &:after?)
284
334
  end
285
-
335
+
336
+ # Removes the specified filters from the filter chain. This only works for method reference (symbol)
337
+ # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that
338
+ # it will match any before, after or yielding around filter.
339
+ #
340
+ # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
341
+ # just like when you apply the filters.
342
+ def skip_filter(*filters)
343
+ skip_filter_in_chain(*filters)
344
+ end
345
+
346
+ # Returns an array of Filter objects for this controller.
347
+ def filter_chain
348
+ read_inheritable_attribute("filter_chain") || []
349
+ end
350
+
286
351
  # Returns all the before filters for this class and all its ancestors.
352
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
287
353
  def before_filters #:nodoc:
288
- @before_filters ||= read_inheritable_attribute("before_filters") || []
354
+ filter_chain.select(&:before?).map(&:filter)
289
355
  end
290
-
356
+
291
357
  # Returns all the after filters for this class and all its ancestors.
358
+ # This method returns the actual filter that was assigned in the controller to maintain existing functionality.
292
359
  def after_filters #:nodoc:
293
- @after_filters ||= read_inheritable_attribute("after_filters") || []
360
+ filter_chain.select(&:after?).map(&:filter)
294
361
  end
295
-
362
+
296
363
  # Returns a mapping between filters and the actions that may run them.
297
364
  def included_actions #:nodoc:
298
- @included_actions ||= read_inheritable_attribute("included_actions") || {}
365
+ read_inheritable_attribute("included_actions") || {}
299
366
  end
300
-
367
+
301
368
  # Returns a mapping between filters and actions that may not run them.
302
369
  def excluded_actions #:nodoc:
303
- @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
370
+ read_inheritable_attribute("excluded_actions") || {}
304
371
  end
305
-
306
- private
307
- def append_filter_to_chain(condition, filters)
308
- write_inheritable_array("#{condition}_filters", filters)
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
+ if (ia = included_actions[filter]) && !ia.empty?
385
+ !ia.include?(action)
386
+ else
387
+ (excluded_actions[filter] || []).include?(action)
388
+ end
389
+ end
390
+
391
+ # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but
392
+ # contains no logic for calling the actual filters.
393
+ class Filter #:nodoc:
394
+ attr_reader :filter, :included_actions, :excluded_actions
395
+
396
+ def initialize(filter)
397
+ @filter = filter
398
+ end
399
+
400
+ def before?
401
+ false
402
+ end
403
+
404
+ def after?
405
+ false
406
+ end
407
+
408
+ def around?
409
+ true
309
410
  end
310
411
 
311
- def prepend_filter_to_chain(condition, filters)
312
- old_filters = read_inheritable_attribute("#{condition}_filters") || []
313
- write_inheritable_attribute("#{condition}_filters", filters + old_filters)
412
+ def call(controller, &block)
413
+ raise(ActionControllerError, 'No filter type: Nothing to do here.')
314
414
  end
415
+ end
416
+
417
+ # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old
418
+ # before_filter and after_filter by moving the logic into the filter itself.
419
+ class FilterProxy < Filter #:nodoc:
420
+ def filter
421
+ @filter.filter
422
+ end
423
+
424
+ def around?
425
+ false
426
+ end
427
+ end
315
428
 
316
- def ensure_filter_responds_to_before_and_after(filter)
317
- unless filter.respond_to?(:before) && filter.respond_to?(:after)
318
- raise ActionControllerError, "Filter object must respond to both before and after"
429
+ class BeforeFilterProxy < FilterProxy #:nodoc:
430
+ def before?
431
+ true
432
+ end
433
+
434
+ def call(controller, &block)
435
+ if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted.
436
+ controller.halt_filter_chain(@filter, :returned_false)
437
+ else
438
+ yield
319
439
  end
320
440
  end
441
+ end
442
+
443
+ class AfterFilterProxy < FilterProxy #:nodoc:
444
+ def after?
445
+ true
446
+ end
321
447
 
322
- def extract_conditions!(filters)
323
- return nil unless filters.last.is_a? Hash
324
- filters.pop
448
+ def call(controller, &block)
449
+ yield
450
+ @filter.call(controller)
325
451
  end
452
+ end
453
+
454
+ class SymbolFilter < Filter #:nodoc:
455
+ def call(controller, &block)
456
+ controller.send(@filter, &block)
457
+ end
458
+ end
459
+
460
+ class ProcFilter < Filter #:nodoc:
461
+ def call(controller)
462
+ @filter.call(controller)
463
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
464
+ raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
465
+ end
466
+ end
467
+
468
+ class ProcWithCallFilter < Filter #:nodoc:
469
+ def call(controller, &block)
470
+ @filter.call(controller, block)
471
+ rescue LocalJumpError # a yield from a proc... no no bad dog.
472
+ raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
473
+ end
474
+ end
475
+
476
+ class MethodFilter < Filter #:nodoc:
477
+ def call(controller, &block)
478
+ @filter.call(controller, &block)
479
+ end
480
+ end
326
481
 
327
- def add_action_conditions(filters, conditions)
328
- return unless conditions
329
- included, excluded = conditions[:only], conditions[:except]
330
- write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
331
- write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
482
+ class ClassFilter < Filter #:nodoc:
483
+ def call(controller, &block)
484
+ @filter.filter(controller, &block)
485
+ end
486
+ end
487
+
488
+ protected
489
+ def append_filter_to_chain(filters, position = :around, &block)
490
+ write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
491
+ end
492
+
493
+ def prepend_filter_to_chain(filters, position = :around, &block)
494
+ write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
495
+ end
496
+
497
+ def create_filters(filters, position, &block) #:nodoc:
498
+ filters, conditions = extract_conditions(filters, &block)
499
+ filters.map! { |filter| find_or_create_filter(filter,position) }
500
+ update_conditions(filters, conditions)
501
+ filters
502
+ end
503
+
504
+ def find_or_create_filter(filter,position)
505
+ if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
506
+ found_filter
507
+ else
508
+ f = class_for_filter(filter).new(filter)
509
+ # apply proxy to filter if necessary
510
+ case position
511
+ when :before
512
+ BeforeFilterProxy.new(f)
513
+ when :after
514
+ AfterFilterProxy.new(f)
515
+ else
516
+ f
517
+ end
518
+ end
519
+ end
520
+
521
+ # The determination of the filter type was once done at run time.
522
+ # This method is here to extract as much logic from the filter run time as possible
523
+ def class_for_filter(filter) #:nodoc:
524
+ case
525
+ when filter.is_a?(Symbol)
526
+ SymbolFilter
527
+ when filter.respond_to?(:call)
528
+ if filter.is_a?(Method)
529
+ MethodFilter
530
+ elsif filter.arity == 1
531
+ ProcFilter
532
+ else
533
+ ProcWithCallFilter
534
+ end
535
+ when filter.respond_to?(:filter)
536
+ ClassFilter
537
+ else
538
+ raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
539
+ end
540
+ end
541
+
542
+ def extract_conditions(*filters, &block) #:nodoc:
543
+ filters.flatten!
544
+ conditions = filters.last.is_a?(Hash) ? filters.pop : {}
545
+ filters << block if block_given?
546
+ return filters, conditions
547
+ end
548
+
549
+ def update_conditions(filters, conditions)
550
+ return if conditions.empty?
551
+ if conditions[:only]
552
+ write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only]))
553
+ else
554
+ write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except]
555
+ end
332
556
  end
333
557
 
334
558
  def condition_hash(filters, *actions)
335
- filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
336
- end
337
-
338
- def remove_contradicting_conditions!(filters, conditions)
339
- return unless conditions[:only]
340
- filters.each do |filter|
341
- next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
342
- [*conditions[:only]].each do |conditional_action|
343
- conditional_action = conditional_action.to_s
344
- included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
559
+ actions = actions.flatten.map(&:to_s)
560
+ filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) }
561
+ end
562
+
563
+ def skip_filter_in_chain(*filters, &test) #:nodoc:
564
+ filters, conditions = extract_conditions(filters)
565
+ filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
566
+ filters.compact!
567
+
568
+ if conditions.empty?
569
+ delete_filters_in_chain(filters)
570
+ else
571
+ remove_actions_from_included_actions!(filters,conditions[:only] || [])
572
+ conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
573
+ update_conditions(filters,conditions)
574
+ end
575
+ end
576
+
577
+ def remove_actions_from_included_actions!(filters,*actions)
578
+ actions = actions.flatten.map(&:to_s)
579
+ updated_hash = filters.inject(included_actions) do |hash,filter|
580
+ ia = (hash[filter] || []) - actions
581
+ ia.blank? ? hash.delete(filter) : hash[filter] = ia
582
+ hash
583
+ end
584
+ write_inheritable_attribute('included_actions', updated_hash)
585
+ end
586
+
587
+ def delete_filters_in_chain(filters) #:nodoc:
588
+ write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })
589
+ end
590
+
591
+ def filter_responds_to_before_and_after(filter) #:nodoc:
592
+ filter.respond_to?(:before) && filter.respond_to?(:after)
593
+ end
594
+
595
+ def proxy_before_and_after_filter(filter) #:nodoc:
596
+ return filter unless filter_responds_to_before_and_after(filter)
597
+ Proc.new do |controller, action|
598
+ unless filter.before(controller) == false
599
+ begin
600
+ action.call
601
+ ensure
602
+ filter.after(controller)
603
+ end
345
604
  end
346
605
  end
347
606
  end
@@ -350,26 +609,14 @@ module ActionController #:nodoc:
350
609
  module InstanceMethods # :nodoc:
351
610
  def self.included(base)
352
611
  base.class_eval do
353
- alias_method :perform_action_without_filters, :perform_action
354
- alias_method :perform_action, :perform_action_with_filters
355
-
356
- alias_method :process_without_filters, :process
357
- alias_method :process, :process_with_filters
358
-
359
- alias_method :process_cleanup_without_filters, :process_cleanup
360
- alias_method :process_cleanup, :process_cleanup_with_filters
612
+ alias_method_chain :perform_action, :filters
613
+ alias_method_chain :process, :filters
614
+ alias_method_chain :process_cleanup, :filters
361
615
  end
362
616
  end
363
617
 
364
618
  def perform_action_with_filters
365
- before_action_result = before_action
366
-
367
- unless before_action_result == false || performed?
368
- perform_action_without_filters
369
- after_action
370
- end
371
-
372
- @before_filter_chain_aborted = (before_action_result == false)
619
+ call_filter(self.class.filter_chain, 0)
373
620
  end
374
621
 
375
622
  def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
@@ -377,61 +624,37 @@ module ActionController #:nodoc:
377
624
  process_without_filters(request, response, method, *arguments)
378
625
  end
379
626
 
380
- # Calls all the defined before-filter filters, which are added by using "before_filter :method".
381
- # If any of the filters return false, no more filters will be executed and the action is aborted.
382
- def before_action #:doc:
383
- call_filters(self.class.before_filters)
627
+ def filter_chain
628
+ self.class.filter_chain
384
629
  end
385
630
 
386
- # Calls all the defined after-filter filters, which are added by using "after_filter :method".
387
- # If any of the filters return false, no more filters will be executed.
388
- def after_action #:doc:
389
- call_filters(self.class.after_filters)
390
- end
391
-
392
- private
393
- def call_filters(filters)
394
- filters.each do |filter|
395
- next if action_exempted?(filter)
396
-
397
- filter_result = case
398
- when filter.is_a?(Symbol)
399
- self.send(filter)
400
- when filter_block?(filter)
401
- filter.call(self)
402
- when filter_class?(filter)
403
- filter.filter(self)
404
- else
405
- raise(
406
- ActionControllerError,
407
- 'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
408
- )
409
- end
631
+ def call_filter(chain, index)
632
+ return (performed? || perform_action_without_filters) if index >= chain.size
633
+ filter = chain[index]
634
+ return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)
410
635
 
411
- if filter_result == false
412
- logger.info "Filter chain halted as [#{filter}] returned false" if logger
413
- return false
414
- end
415
- end
416
- end
417
-
418
- def filter_block?(filter)
419
- filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
420
- end
421
-
422
- def filter_class?(filter)
423
- filter.respond_to?('filter')
636
+ halted = false
637
+ filter.call(self) do
638
+ halted = call_filter(chain, index.next)
424
639
  end
640
+ halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
641
+ halted
642
+ end
425
643
 
426
- def action_exempted?(filter)
427
- case
428
- when ia = self.class.included_actions[filter]
429
- !ia.include?(action_name)
430
- when ea = self.class.excluded_actions[filter]
431
- ea.include?(action_name)
644
+ def halt_filter_chain(filter, reason)
645
+ if logger
646
+ case reason
647
+ when :no_yield
648
+ logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
649
+ when :returned_false
650
+ logger.info "Filter chain halted as [#{filter.inspect}] returned false."
432
651
  end
433
652
  end
653
+ @before_filter_chain_aborted = true
654
+ return false
655
+ end
434
656
 
657
+ private
435
658
  def process_cleanup_with_filters
436
659
  if @before_filter_chain_aborted
437
660
  close_session