Changeset 5163
- Timestamp:
- 09/22/06 03:41:03 (3 years ago)
- Files:
-
- trunk/actionpack/CHANGELOG (modified) (1 diff)
- trunk/actionpack/lib/action_controller/filters.rb (modified) (8 diffs)
- trunk/actionpack/test/active_record_unit.rb (modified) (1 diff)
- trunk/actionpack/test/controller/filters_test.rb (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/CHANGELOG
r5155 r5163 1 1 *SVN* 2 3 * Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper] 2 4 3 5 * Update RJS render tests. [sam] trunk/actionpack/lib/action_controller/filters.rb
r4312 r5163 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 newfilters without15 # Controller inheritance hierarchies share filters downwards, but subclasses can also add or skip filters without 21 16 # affecting the superclass. For example: 22 17 # … … 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 # … … 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 # … … 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 # … … 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 # … … 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 112 # end 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}" 124 # end 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 113 131 # 114 132 # class BenchmarkingFilter 115 # def initialize116 # @runtime133 # 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 filter174 # # Will run the :authenticate and :catch_exceptions filters. 141 175 # end 142 176 # 143 177 # class SignupController < ApplicationController 144 # # will not run the :authenticate filter178 # # 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 to151 # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both152 # o f 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 authenticated210 # # 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_ actions173 # on this controller areperformed.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) 179 end 180 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. 251 append_filter_to_chain(filters, :before, &block) 252 end 253 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) 188 end 189 190 # Short-hand for append_before_filter since that's the most common of the two. 257 prepend_filter_to_chain(filters, :before, &block) 258 end 259 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_ actions194 # 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) 200 end 201 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. 266 prepend_filter_to_chain(filters, :after, &block) 267 end 268 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) 209 end 210 211 # Short-hand for append_after_filter since that's the most common of the two. 272 append_filter_to_chain(filters, :after, &block) 273 end 274 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) } 244 end 245 end 246 247 # Short-hand for append_around_filter since that's the most common of the two. 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]) 310 end 311 end 312 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 266 end 267 268 # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference 323 skip_filter_in_chain(*filters, &:before?) 324 end 325 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 284 end 285 333 skip_filter_in_chain(*filters, &:after?) 334 end 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") || []289 end 290 354 filter_chain.select(&:before?).map(&:filter) 355 end 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") || []294 end 295 360 filter_chain.select(&:after?).map(&:filter) 361 end 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") || {}299 end 300 365 filter_chain.inject({}) { |h,f| h[f.filter] = f.included_actions; h } 366 end 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") || {} 304 end 305 306 private 307 def append_filter_to_chain(condition, filters) 308 write_inheritable_array("#{condition}_filters", filters) 309 end 310 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) 314 end 315 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" 319 end 320 end 321 322 def extract_conditions!(filters) 323 return nil unless filters.last.is_a? Hash 324 filters.pop 325 end 326 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 332 end 333 334 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) 370 filter_chain.inject({}) { |h,f| h[f.filter] = f.excluded_actions; h } 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 # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but 383 # contains no logic for calling the actual filters. 384 class Filter #:nodoc: 385 attr_reader :filter, :included_actions, :excluded_actions 386 387 def initialize(filter, options={}) 388 @filter = filter 389 @position = options[:position] 390 update_conditions(options) 391 end 392 393 def update_conditions(conditions) 394 if conditions[:only] 395 @included_actions = [conditions[:only]].flatten.map(&:to_s) 396 else 397 @excluded_actions = [conditions[:except]].flatten.compact.map(&:to_s) 398 end 399 build_excluded_actions_hash 400 end 401 402 def remove_actions_from_included_actions!(*actions) 403 if @included_actions 404 actions.flatten.map(&:to_s).each { |action| @included_actions.delete(action) } 405 build_excluded_actions_hash 406 end 407 end 408 409 def before? 410 false 411 end 412 413 def after? 414 false 415 end 416 417 def excluded_from?(action) 418 @excluded_actions_hash[action] 419 end 420 421 def call(controller, &block) 422 raise(ActionControllerError, 'No filter type: Nothing to do here.') 423 end 424 425 private 426 def build_excluded_actions_hash 427 @excluded_actions_hash = 428 if @included_actions 429 @included_actions.inject(Hash.new(true)){|h, a| h[a] = false; h} 430 else 431 @excluded_actions.inject(Hash.new(false)){|h, a| h[a] = true; h} 432 end 433 end 434 end 435 436 # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old 437 # before_filter and after_filter by moving the logic into the filter itself. 438 class FilterProxy < Filter #:nodoc: 439 def filter 440 @filter.filter 441 end 442 end 443 444 class BeforeFilterProxy < FilterProxy #:nodoc: 445 def before? 446 true 447 end 448 449 def call(controller, &block) 450 if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted. 451 controller.halt_filter_chain(@filter, :returned_false) 452 else 453 yield 454 end 455 end 456 end 457 458 class AfterFilterProxy < FilterProxy #:nodoc: 459 def after? 460 true 461 end 462 463 def call(controller, &block) 464 yield 465 @filter.call(controller) 466 end 467 end 468 469 class SymbolFilter < Filter #:nodoc: 470 def call(controller, &block) 471 controller.send(@filter, &block) 472 end 473 end 474 475 class ProcFilter < Filter #:nodoc: 476 def call(controller) 477 @filter.call(controller) 478 rescue LocalJumpError # a yield from a proc... no no bad dog. 479 raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') 480 end 481 end 482 483 class ProcWithCallFilter < Filter #:nodoc: 484 def call(controller, &block) 485 @filter.call(controller, block) 486 rescue LocalJumpError # a yield from a proc... no no bad dog. 487 raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') 488 end 489 end 490 491 class MethodFilter < Filter #:nodoc: 492 def call(controller, &block) 493 @filter.call(controller, &block) 494 end 495 end 496 497 class ClassFilter < Filter #:nodoc: 498 def call(controller, &block) 499 @filter.filter(controller, &block) 500 end 501 end 502 503 protected 504 def append_filter_to_chain(filters, position = :around, &block) 505 write_inheritable_array('filter_chain', create_filters(filters, position, &block) ) 506 end 507 508 def prepend_filter_to_chain(filters, position = :around, &block) 509 write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain) 510 end 511 512 def create_filters(filters, position, &block) #:nodoc: 513 filters, conditions = extract_conditions(filters, &block) 514 conditions.merge!(:position => position) 515 516 # conditions should be blank for a filter behind a filter proxy since the proxy will take care of all conditions. 517 proxy_conditions, conditions = conditions, {} unless :around == position 518 519 filters.map do |filter| 520 # change all the filters into instances of Filter derived classes 521 class_for_filter(filter).new(filter, conditions) 522 end.collect do |filter| 523 # apply proxy to filter if necessary 524 case position 525 when :before 526 BeforeFilterProxy.new(filter, proxy_conditions) 527 when :after 528 AfterFilterProxy.new(filter, proxy_conditions) 529 else 530 filter 345 531 end 346 532 end 533 end 534 535 # The determination of the filter type was once done at run time. 536 # This method is here to extract as much logic from the filter run time as possible 537 def class_for_filter(filter) #:nodoc: 538 case 539 when filter.is_a?(Symbol) 540 SymbolFilter 541 when filter.respond_to?(:call) 542 if filter.is_a?(Method) 543 MethodFilter 544 elsif filter.arity == 1 545 ProcFilter 546 else 547 ProcWithCallFilter 548 end 549 when filter.respond_to?(:filter) 550 ClassFilter 551 else 552 raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.') 553 end 554 end 555 556 def filter_responds_to_before_and_after(filter) #:nodoc: 557 filter.respond_to?(:before) && filter.respond_to?(:after) 558 end 559 560 def proxy_before_and_after_filter(filter) #:nodoc: 561 return filter unless filter_responds_to_before_and_after(filter) 562 Proc.new do |controller, action| 563 unless filter.before(controller) == false 564 begin 565 action.call 566 ensure 567 filter.after(controller) 568 end 569 end 570 end 571 end 572 573 def extract_conditions(*filters, &block) #:nodoc: 574 filters.flatten! 575 conditions = filters.last.is_a?(Hash) ? filters.pop : {} 576 filters << block if block_given? 577 return filters.flatten, conditions 578 end 579 580 def skip_filter_in_chain(*filters, &test) #:nodoc: 581 filters, conditions = extract_conditions(filters) 582 finder_proc = block_given? ? 583 proc { |f| find_filter(f, &test) } : 584 proc { |f| find_filter(f) } 585 586 filters.map(&finder_proc).reject(&:nil?).each do |filter| 587 if conditions.empty? 588 delete_filter_in_chain(filter) 589 else 590 filter.remove_actions_from_included_actions!(conditions[:only] || []) 591 conditions[:only], conditions[:except] = conditions[:except], conditions[:only] 592 filter.update_conditions(conditions) 593 end 594 end 595 end 596 597 def delete_filter_in_chain(filter) #:nodoc: 598 write_inheritable_attribute('filter_chain', filter_chain.reject { |f| f == filter }) 347 599 end 348 600 end … … 358 610 359 611 def perform_action_with_filters 360 before_action_result = before_action 361 362 unless before_action_result == false || performed? 363 perform_action_without_filters 364 after_action 365 end 366 367 @before_filter_chain_aborted = (before_action_result == false) 612 #result = perform_filters do 613 # perform_action_without_filters unless performed? 614 #end 615 @before_filter_chain_aborted = (call_filter(self.class.filter_chain, 0) == false) 368 616 end 369 617 … … 373 621 end 374 622 375 # Calls all the defined before-filter filters, which are added by using "before_filter :method". 376 # If any of the filters return false, no more filters will be executed and the action is aborted. 377 def before_action #:doc: 378 call_filters(self.class.before_filters) 379 end 380 381 # Calls all the defined after-filter filters, which are added by using "after_filter :method". 382 # If any of the filters return false, no more filters will be executed. 383 def after_action #:doc: 384 call_filters(self.class.after_filters) 385 end 386 623 def filter_chain 624 self.class.filter_chain 625 end 626 627 def call_filter(chain, index) 628 return (performed? || perform_action_without_filters) if index >= chain.size 629 filter = chain[index] 630 return call_filter(chain, index.next) if filter.excluded_from?(action_name) 631 632 called = false 633 filter.call(self) do 634 call_filter(chain, index.next) 635 called = true 636 end 637 halt_filter_chain(filter.filter, :no_yield) if called == false 638 called 639 end 640 641 def halt_filter_chain(filter, reason) 642 if logger 643 case reason 644 when :no_yield 645 logger.info "Filter chain halted as [#{filter.inspect}] did not yield." 646 when :returned_false 647 logger.info "Filter chain halted as [#{filter.inspect}] returned false." 648 end 649 end 650 return false 651 end 652 387 653 private 388 def call_filters(filters)389 filters.each do |filter|390 next if action_exempted?(filter)391 392 filter_result = case393 when filter.is_a?(Symbol)394 self.send(filter)395 when filter_block?(filter)396 filter.call(self)397 when filter_class?(filter)398 filter.filter(self)399 else400 raise(401 ActionControllerError,402 'Filters need to be either a symbol, proc/method, or class implementing a static filter method'403 )404 end405 406 if filter_result == false407 logger.info "Filter chain halted as [#{filter}] returned false" if logger408 return false409 end410 end411 end412 413 def filter_block?(filter)414 filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)415 end416 417 def filter_class?(filter)418 filter.respond_to?('filter')419 end420 421 def action_exempted?(filter)422 case423 when ia = self.class.included_actions[filter]424 !ia.include?(action_name)425 when ea = self.class.excluded_actions[filter]426 ea.include?(action_name)427 end428 end429 430 654 def process_cleanup_with_filters 431 655 if @before_filter_chain_aborted trunk/actionpack/test/active_record_unit.rb
r4885 r5163 107 107 # If things go wrong, we don't want to run our test cases. We'll just define them to test nothing. 108 108 def abort_tests 109 $stderr.puts 'No Active Record connection, aborting tests.' 109 110 self.class.public_instance_methods.grep(/^test./).each do |method| 110 111 self.class.class_eval { define_method(method.to_sym){} } trunk/actionpack/test/controller/filters_test.rb
r4178 r5163 197 197 render_text "hello" 198 198 end 199 end200 201 class BadFilterController < ActionController::Base202 before_filter 2203 204 def show() "show" end205 206 protected207 def rescue_action(e) raise(e) end208 199 end 209 200 … … 337 328 assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"] 338 329 end 339 330 340 331 def test_bad_filter 341 assert_raises(ActionController::ActionControllerError) { 342 test_process(BadFilterController) 343 } 344 end 345 332 bad_filter_controller = Class.new(ActionController::Base) 333 assert_raises(ActionController::ActionControllerError) do 334 bad_filter_controller.before_filter 2 335 end 336 end 337 346 338 def test_around_filter 347 339 controller = test_process(AroundFilterController) … … 349 341 assert controller.template.assigns["after_ran"] 350 342 end 351 343 352 344 def test_having_properties_in_around_filter 353 345 controller = test_process(AroundFilterController) … … 409 401 end 410 402 end 403 404 405 406 class PostsController < ActionController::Base 407 def rescue_action(e); raise e; end 408 409 module AroundExceptions 410 class Error < StandardError ; end 411 class Before < Error ; end 412 class After < Error ; end 413 end 414 include AroundExceptions 415 416 class DefaultFilter 417 include AroundExceptions 418 end 419 420 module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n") 421 422 private 423 def default_action 424 render :inline => "#{action_name} called" 425 end 426 end 427 428 class ControllerWithSymbolAsFilter < PostsController 429 around_filter :raise_before, :only => :raises_before 430 around_filter :raise_after, :only => :raises_after 431 around_filter :without_exception, :only => :no_raise 432 433 private 434 def raise_before 435 raise Before 436 yield 437 end 438 439 def raise_after 440 yield 441 raise After 442 end 443 444 def without_exception 445 # Do stuff... 446 1 + 1 447 448 yield 449 450 # Do stuff... 451 1 + 1 452 end 453 end 454 455 class ControllerWithFilterClass < PostsController 456 class YieldingFilter < DefaultFilter 457 def self.filter(controller) 458 yield 459 raise After 460 end 461 end 462 463 around_filter YieldingFilter, :only => :raises_after 464 end 465 466 class ControllerWithFilterInstance < PostsController 467 class YieldingFilter < DefaultFilter 468 def filter(controller) 469 yield 470 raise After 471 end 472 end 473 474 around_filter YieldingFilter.new, :only => :raises_after 475 end 476 477 class ControllerWithFilterMethod < PostsController 478 class YieldingFilter < DefaultFilter 479 def filter(controller) 480 yield 481 raise After 482 end 483 end 484 485 around_filter YieldingFilter.new.method(:filter), :only => :raises_after 486 end 487 488 class ControllerWithProcFilter < PostsController 489 around_filter(:only => :no_raise) do |c,b| 490 c.assigns['before'] = true 491 b.call 492 c.assigns['after'] = true 493 end 494 end 495 496 class ControllerWithWrongFilterType < PostsController 497 around_filter lambda { yield }, :only => :no_raise 498 end 499 500 class ControllerWithNestedFilters < ControllerWithSymbolAsFilter 501 around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both 502 end 503 504 class ControllerWithAllTypesOfFilters < PostsController 505 before_filter :before 506 around_filter :around 507 after_filter :after 508 around_filter :around_again 509 510 private 511 def before 512 @ran_filter ||= [] 513 @ran_filter << 'before' 514 end 515 516 def around 517 @ran_filter << 'around (before yield)' 518 yield 519 @ran_filter << 'around (after yield)' 520 end 521 522 def after 523 @ran_filter << 'after' 524 end 525 526 def around_again 527 @ran_filter << 'around_again (before yield)' 528 yield 529 @ran_filter << 'around_again (after yield)' 530 end 531 end 532 533 class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters 534 skip_filter :around_again 535 skip_filter :after 536 end 537 538 class YieldingAroundFiltersTest < Test::Unit::TestCase 539 include PostsController::AroundExceptions 540 541 def test_filters_registering 542 assert_equal 1, ControllerWithFilterMethod.filter_chain.size 543 assert_equal 1, ControllerWithFilterClass.filter_chain.size 544 assert_equal 1, ControllerWithFilterInstance.filter_chain.size 545 assert_equal 3, ControllerWithSymbolAsFilter.filter_chain.size 546 assert_equal 1, ControllerWithWrongFilterType.filter_chain.size 547 assert_equal 6, ControllerWithNestedFilters.filter_chain.size 548 assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size 549 end 550 551 def test_wrong_filter_type 552 assert_raise(ActionController::ActionControllerError) do 553 test_process(ControllerWithWrongFilterType,'no_raise') 554 end 555 end 556 557 def test_base 558 controller = PostsController 559 assert_nothing_raised { test_process(controller,'no_raise') } 560 assert_nothing_raised { test_process(controller,'raises_before') } 561 assert_nothing_raised { test_process(controller,'raises_after') } 562 assert_nothing_raised { test_process(controller,'no_filter') } 563 end 564 565 def test_with_symbol 566 controller = ControllerWithSymbolAsFilter 567 assert_nothing_raised { test_process(controller,'no_raise') } 568 assert_raise(Before) { test_process(controller,'raises_before') } 569 assert_raise(After) { test_process(controller,'raises_after') } 570 assert_nothing_raised { test_process(controller,'no_raise') } 571 end 572 573 def test_with_class 574 controller = ControllerWithFilterClass 575 assert_nothing_raised { test_process(controller,'no_raise') } 576 assert_raise(After) { test_process(controller,'raises_after') } 577 end 578 579 def test_with_instance 580 controller = ControllerWithFilterInstance 581 assert_nothing_raised { test_process(controller,'no_raise') } 582 assert_raise(After) { test_process(controller,'raises_after') } 583 end 584 585 def test_with_method 586 controller = ControllerWithFilterMethod 587 assert_nothing_raised { test_process(controller,'no_raise') } 588 assert_raise(After) { test_process(controller,'raises_after') } 589 end 590 591 def test_with_proc 592 controller = test_process(ControllerWithProcFilter,'no_raise') 593 assert controller.template.assigns['before'] 594 assert controller.template.assigns['after'] 595 end 596 597 def test_nested_filters 598 controller = ControllerWithNestedFilters 599 assert_nothing_raised do 600 begin 601 test_process(controller,'raises_both') 602 rescue Before, After 603 end 604 end 605 assert_raise Before do 606 begin 607 test_process(controller,'raises_both') 608 rescue After 609 end 610 end 611 end 612 613 def test_filter_order_with_all_filter_types 614 controller = test_process(ControllerWithAllTypesOfFilters,'no_raise') 615 assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after',controller.template.assigns['ran_filter'].join(' ') 616 end 617 618 def test_filter_order_with_skip_filter_method 619 controller = test_process(ControllerWithTwoLessFilters,'no_raise') 620 assert_equal 'before around (before yield) around (after yield)',controller.template.assigns['ran_filter'].join(' ') 621 end 622 623 protected 624 def test_process(controller, action = "show") 625 request = ActionController::TestRequest.new 626 request.action = action 627 controller.process(request, ActionController::TestResponse.new) 628 end 629 end