Ticket #11235: refactor_filters.diff
| File refactor_filters.diff, 39.3 kB (added by josh, 7 months ago) |
|---|
-
a/actionpack/lib/action_controller/dispatcher.rb
old new 20 20 # existing callback. Passing an identifier is a suggested practice if the 21 21 # code adding a preparation block may be reloaded. 22 22 def to_prepare(identifier = nil, &block) 23 @prepare_dispatch_callbacks ||= []23 @prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new 24 24 callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier) 25 26 # Already registered: update the existing callback 27 # TODO: Ruby one liner for Array#find returning index 28 if identifier && callback_for_identifier = @prepare_dispatch_callbacks.find { |c| c.identifier == identifier } 29 index = @prepare_dispatch_callbacks.index(callback_for_identifier) 30 @prepare_dispatch_callbacks[index] = callback 31 else 32 @prepare_dispatch_callbacks.concat([callback]) 33 end 25 @prepare_dispatch_callbacks.replace_or_append_callback(callback) 34 26 end 35 27 36 28 # If the block raises, send status code as a last-ditch response. -
a/actionpack/lib/action_controller/filters.rb
old new 244 244 # filter and controller action will not be run. If #before renders or redirects, 245 245 # the second half of #around and will still run but #after and the 246 246 # action will not. If #around fails to yield, #after will not be run. 247 248 class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc: 249 def append_filter_to_chain(filters, filter_type, &block) 250 pos = find_filter_append_position(filters, filter_type) 251 update_filter_chain(filters, filter_type, pos, &block) 252 end 253 254 def prepend_filter_to_chain(filters, filter_type, &block) 255 pos = find_filter_prepend_position(filters, filter_type) 256 update_filter_chain(filters, filter_type, pos, &block) 257 end 258 259 def create_filters(filters, filter_type, &block) 260 filters, conditions = extract_options(filters, &block) 261 filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) } 262 filters 263 end 264 265 def skip_filter_in_chain(*filters, &test) 266 filters, conditions = extract_options(filters) 267 update_filter_in_chain(filters, :skip => conditions, &test) 268 end 269 270 private 271 def update_filter_chain(filters, filter_type, pos, &block) 272 new_filters = create_filters(filters, filter_type, &block) 273 insert(pos, new_filters).flatten! 274 end 275 276 def find_filter_append_position(filters, filter_type) 277 # appending an after filter puts it at the end of the call chain 278 # before and around filters go before the first after filter in the chain 279 unless filter_type == :after 280 each_with_index do |f,i| 281 return i if f.after? 282 end 283 end 284 return -1 285 end 286 287 def find_filter_prepend_position(filters, filter_type) 288 # prepending a before or around filter puts it at the front of the call chain 289 # after filters go before the first after filter in the chain 290 if filter_type == :after 291 each_with_index do |f,i| 292 return i if f.after? 293 end 294 return -1 295 end 296 return 0 297 end 298 299 def find_or_create_filter(filter, filter_type, options = {}) 300 update_filter_in_chain([filter], options) 301 302 if found_filter = find_callback(filter) { |f| f.type == filter_type } 303 found_filter 304 else 305 filter_kind = case 306 when filter.respond_to?(:before) && filter_type == :before 307 :before 308 when filter.respond_to?(:after) && filter_type == :after 309 :after 310 else 311 :filter 312 end 313 314 case filter_type 315 when :before 316 BeforeFilter.new(filter_kind, filter, options) 317 when :after 318 AfterFilter.new(filter_kind, filter, options) 319 else 320 AroundFilter.new(filter_kind, filter, options) 321 end 322 end 323 end 324 325 def update_filter_in_chain(filters, options, &test) 326 filters.map! { |f| block_given? ? find_callback(f, &test) : find_callback(f) } 327 filters.compact! 328 329 map! do |filter| 330 if filters.include?(filter) 331 new_filter = filter.dup 332 new_filter.options.merge!(options) 333 new_filter 334 else 335 filter 336 end 337 end 338 end 339 end 340 341 class Filter < ActiveSupport::Callbacks::Callback #:nodoc: 342 def before? 343 self.class == BeforeFilter 344 end 345 346 def after? 347 self.class == AfterFilter 348 end 349 350 def around? 351 self.class == AroundFilter 352 end 353 354 private 355 def should_not_skip?(controller) 356 if options[:skip] 357 !included_in_action?(controller, options[:skip]) 358 else 359 true 360 end 361 end 362 363 def included_in_action?(controller, options) 364 if options[:only] 365 Array(options[:only]).map(&:to_s).include?(controller.action_name) 366 elsif options[:except] 367 !Array(options[:except]).map(&:to_s).include?(controller.action_name) 368 else 369 true 370 end 371 end 372 373 def should_run_callback?(controller) 374 should_not_skip?(controller) && included_in_action?(controller, options) && super 375 end 376 end 377 378 class AroundFilter < Filter #:nodoc: 379 def type 380 :around 381 end 382 383 def call(controller, &block) 384 if should_run_callback?(controller) 385 proc = filter_responds_to_before_and_after? ? around_proc : method 386 evaluate_method(proc, controller, &block) 387 else 388 block.call 389 end 390 end 391 392 private 393 def filter_responds_to_before_and_after? 394 method.respond_to?(:before) && method.respond_to?(:after) 395 end 396 397 def around_proc 398 Proc.new do |controller, action| 399 method.before(controller) 400 401 if controller.send!(:performed?) 402 controller.send!(:halt_filter_chain, method, :rendered_or_redirected) 403 else 404 begin 405 action.call 406 ensure 407 method.after(controller) 408 end 409 end 410 end 411 end 412 end 413 414 class BeforeFilter < Filter #:nodoc: 415 def type 416 :before 417 end 418 419 def call(controller, &block) 420 super 421 if controller.send!(:performed?) 422 controller.send!(:halt_filter_chain, method, :rendered_or_redirected) 423 end 424 end 425 end 426 427 class AfterFilter < Filter #:nodoc: 428 def type 429 :after 430 end 431 end 432 247 433 module ClassMethods 248 434 # The passed <tt>filters</tt> will be appended to the filter_chain and 249 435 # will execute before the action on this controller is performed. 250 436 def append_before_filter(*filters, &block) 251 append_filter_to_chain(filters, :before, &block)437 filter_chain.append_filter_to_chain(filters, :before, &block) 252 438 end 253 439 254 440 # The passed <tt>filters</tt> will be prepended to the filter_chain and 255 441 # will execute before the action on this controller is performed. 256 442 def prepend_before_filter(*filters, &block) 257 prepend_filter_to_chain(filters, :before, &block)443 filter_chain.prepend_filter_to_chain(filters, :before, &block) 258 444 end 259 445 260 446 # Shorthand for append_before_filter since it's the most common. … … 263 449 # The passed <tt>filters</tt> will be appended to the array of filters 264 450 # that run _after_ actions on this controller are performed. 265 451 def append_after_filter(*filters, &block) 266 append_filter_to_chain(filters, :after, &block)452 filter_chain.append_filter_to_chain(filters, :after, &block) 267 453 end 268 454 269 455 # The passed <tt>filters</tt> will be prepended to the array of filters 270 456 # that run _after_ actions on this controller are performed. 271 457 def prepend_after_filter(*filters, &block) 272 prepend_filter_to_chain(filters, :after, &block)458 filter_chain.prepend_filter_to_chain(filters, :after, &block) 273 459 end 274 460 275 461 # Shorthand for append_after_filter since it's the most common. 276 462 alias :after_filter :append_after_filter 277 463 278 279 464 # If you append_around_filter A.new, B.new, the filter chain looks like 280 465 # 281 466 # B#before … … 287 472 # With around filters which yield to the action block, #before and #after 288 473 # are the code before and after the yield. 289 474 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 475 filter_chain.append_filter_to_chain(filters, :around, &block) 294 476 end 295 477 296 478 # If you prepend_around_filter A.new, B.new, the filter chain looks like: … … 304 486 # With around filters which yield to the action block, #before and #after 305 487 # are the code before and after the yield. 306 488 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 489 filter_chain.prepend_filter_to_chain(filters, :around, &block) 311 490 end 312 491 313 492 # Shorthand for append_around_filter since it's the most common. … … 320 499 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 321 500 # just like when you apply the filters. 322 501 def skip_before_filter(*filters) 323 skip_filter_in_chain(*filters, &:before?)502 filter_chain.skip_filter_in_chain(*filters, &:before?) 324 503 end 325 504 326 505 # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference … … 330 509 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 331 510 # just like when you apply the filters. 332 511 def skip_after_filter(*filters) 333 skip_filter_in_chain(*filters, &:after?)512 filter_chain.skip_filter_in_chain(*filters, &:after?) 334 513 end 335 514 336 515 # Removes the specified filters from the filter chain. This only works for method reference (symbol) … … 340 519 # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 341 520 # just like when you apply the filters. 342 521 def skip_filter(*filters) 343 skip_filter_in_chain(*filters)522 filter_chain.skip_filter_in_chain(*filters) 344 523 end 345 524 346 525 # Returns an array of Filter objects for this controller. 347 526 def filter_chain 348 read_inheritable_attribute("filter_chain") || [] 527 if chain = read_inheritable_attribute('filter_chain') 528 return chain 529 else 530 write_inheritable_attribute('filter_chain', FilterChain.new) 531 return filter_chain 532 end 349 533 end 350 534 351 535 # Returns all the before filters for this class and all its ancestors. 352 536 # This method returns the actual filter that was assigned in the controller to maintain existing functionality. 353 537 def before_filters #:nodoc: 354 filter_chain.select(&:before?).map(&: filter)538 filter_chain.select(&:before?).map(&:method) 355 539 end 356 540 357 541 # Returns all the after filters for this class and all its ancestors. 358 542 # This method returns the actual filter that was assigned in the controller to maintain existing functionality. 359 543 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 544 filter_chain.select(&:after?).map(&:method) 380 545 end 381 382 # Returns true if the filter is excluded from the given action383 def filter_excluded_from_action?(filter,action) #:nodoc:384 case385 when ia = included_actions[filter]386 !ia.include?(action)387 when ea = excluded_actions[filter]388 ea.include?(action)389 end390 end391 392 # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but393 # contains no logic for calling the actual filters.394 class Filter #:nodoc:395 attr_reader :filter, :included_actions, :excluded_actions396 397 def initialize(filter)398 @filter = filter399 end400 401 def type402 :around403 end404 405 def before?406 type == :before407 end408 409 def after?410 type == :after411 end412 413 def around?414 type == :around415 end416 417 def run(controller)418 raise ActionControllerError, 'No filter type: Nothing to do here.'419 end420 421 def call(controller, &block)422 run(controller)423 end424 end425 426 # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old427 # before_filter and after_filter by moving the logic into the filter itself.428 class FilterProxy < Filter #:nodoc:429 def filter430 @filter.filter431 end432 end433 434 class BeforeFilterProxy < FilterProxy #:nodoc:435 def type436 :before437 end438 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 end445 end446 447 def call(controller)448 yield unless run(controller)449 end450 end451 452 class AfterFilterProxy < FilterProxy #:nodoc:453 def type454 :after455 end456 457 def run(controller)458 @filter.call(controller)459 end460 461 def call(controller)462 yield463 run(controller)464 end465 end466 467 class SymbolFilter < Filter #:nodoc:468 def call(controller, &block)469 controller.send!(@filter, &block)470 end471 end472 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 end479 end480 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 end487 end488 489 class MethodFilter < Filter #:nodoc:490 def call(controller, &block)491 @filter.call(controller, &block)492 end493 end494 495 class ClassFilter < Filter #:nodoc:496 def call(controller, &block)497 @filter.filter(controller, &block)498 end499 end500 501 class ClassBeforeFilter < Filter #:nodoc:502 def call(controller, &block)503 @filter.before(controller)504 end505 end506 507 class ClassAfterFilter < Filter #:nodoc:508 def call(controller, &block)509 @filter.after(controller)510 end511 end512 513 protected514 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 end518 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 end523 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).flatten527 write_inheritable_attribute('filter_chain', new_chain)528 end529 530 def find_filter_append_position(filters, filter_type)531 # appending an after filter puts it at the end of the call chain532 # before and around filters go before the first after filter in the chain533 unless filter_type == :after534 filter_chain.each_with_index do |f,i|535 return i if f.after?536 end537 end538 return -1539 end540 541 def find_filter_prepend_position(filters, filter_type)542 # prepending a before or around filter puts it at the front of the call chain543 # after filters go before the first after filter in the chain544 if filter_type == :after545 filter_chain.each_with_index do |f,i|546 return i if f.after?547 end548 return -1549 end550 return 0551 end552 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 filters558 end559 560 def find_or_create_filter(filter, filter_type)561 if found_filter = find_filter(filter) { |f| f.type == filter_type }562 found_filter563 else564 f = class_for_filter(filter, filter_type).new(filter)565 # apply proxy to filter if necessary566 case filter_type567 when :before568 BeforeFilterProxy.new(f)569 when :after570 AfterFilterProxy.new(f)571 else572 f573 end574 end575 end576 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 possible579 def class_for_filter(filter, filter_type) #:nodoc:580 case581 when filter.is_a?(Symbol)582 SymbolFilter583 when filter.respond_to?(:call)584 if filter.is_a?(Method)585 MethodFilter586 else587 case filter.arity588 when 1; ProcFilter589 when 2; ProcWithCallFilter590 else raise ArgumentError, 'Filter blocks must take one or two arguments.'591 end592 end593 when filter.respond_to?(:filter)594 ClassFilter595 when filter.respond_to?(:before) && filter_type == :before596 ClassBeforeFilter597 when filter.respond_to?(:after) && filter_type == :after598 ClassAfterFilter599 else600 raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.')601 end602 end603 604 def extract_conditions(*filters, &block) #:nodoc:605 filters.flatten!606 conditions = filters.extract_options!607 filters << block if block_given?608 return filters, conditions609 end610 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 end618 end619 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 end624 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 else633 remove_actions_from_included_actions!(filters,conditions[:only] || [])634 conditions[:only], conditions[:except] = conditions[:except], conditions[:only]635 update_conditions(filters,conditions)636 end637 end638 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] || []) - actions643 ia.empty? ? hash.delete(filter) : hash[filter] = ia644 hash645 end646 write_inheritable_attribute('included_actions', updated_hash)647 end648 649 def delete_filters_in_chain(filters) #:nodoc:650 write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) })651 end652 653 def filter_responds_to_before_and_after(filter) #:nodoc:654 filter.respond_to?(:before) && filter.respond_to?(:after)655 end656 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 else665 begin666 action.call667 ensure668 filter.after(controller)669 end670 end671 end672 end673 546 end 674 547 675 548 module InstanceMethods # :nodoc: … … 681 554 end 682 555 683 556 protected 557 def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: 558 @before_filter_chain_aborted = false 559 process_without_filters(request, response, method, *arguments) 560 end 684 561 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 562 def perform_action_with_filters 563 call_filters(self.class.filter_chain, 0, 0) 564 end 693 565 694 566 private 567 def call_filters(chain, index, nesting) 568 index = run_before_filters(chain, index, nesting) 569 aborted = @before_filter_chain_aborted 570 perform_action_without_filters unless performed? || aborted 571 return index if nesting != 0 || aborted 572 run_after_filters(chain, index) 573 end 574 575 def run_before_filters(chain, index, nesting) 576 while chain[index] 577 filter, index = chain[index], index 578 break unless filter # end of call chain reached 579 580 case filter 581 when BeforeFilter 582 filter.call(self) # invoke before filter 583 index = index.next 584 break if @before_filter_chain_aborted 585 when AroundFilter 586 yielded = false 587 588 filter.call(self) do 589 yielded = true 590 # all remaining before and around filters will be run in this call 591 index = call_filters(chain, index.next, nesting.next) 592 end 695 593 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 594 halt_filter_chain(filter, :did_not_yield) unless yielded 715 595 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) 596 break 597 else 598 break # no before or around filters left 728 599 end 729 730 halt_filter_chain(filter, :did_not_yield) unless yielded731 732 break733 else734 break # no before or around filters left735 600 end 601 602 index 736 603 end 737 604 738 index739 end605 def run_after_filters(chain, index) 606 seen_after_filter = false 740 607 741 def run_after_filters(chain, index) 742 seen_after_filter = false 608 while chain[index] 609 filter, index = chain[index], index 610 break unless filter # end of call chain reached 743 611 744 while chain[index] 745 filter, index = skip_excluded_filters(chain, index) 746 break unless filter # end of call chain reached 612 case filter 613 when AfterFilter 614 seen_after_filter = true 615 filter.call(self) # invoke after filter 616 else 617 # implementation error or someone has mucked with the filter chain 618 raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter 619 end 747 620 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 621 index = index.next 755 622 end 756 623 757 index = index.next624 index.next 758 625 end 759 626 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 627 def halt_filter_chain(filter, reason) 628 @before_filter_chain_aborted = true 629 logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger 630 end 767 631 end 768 632 end 769 633 end -
a/actionpack/test/controller/dispatcher_test.rb
old new 11 11 @output = StringIO.new 12 12 ENV['REQUEST_METHOD'] = 'GET' 13 13 14 Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", [])14 Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) 15 15 @dispatcher = Dispatcher.new(@output) 16 16 end 17 17 -
a/actionpack/test/controller/filters_test.rb
old new 134 134 before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true} 135 135 end 136 136 137 class ConditionalOptionsFilter < ConditionalFilterController 138 before_filter :ensure_login, :if => Proc.new { |c| true } 139 before_filter :clean_up_tmp, :if => Proc.new { |c| false } 140 end 141 137 142 class EmptyFilterChainController < TestController 138 143 self.filter_chain.clear 139 144 def show … … 466 471 assert !response.template.assigns["ran_proc_filter2"] 467 472 end 468 473 474 def test_running_conditional_options 475 response = test_process(ConditionalOptionsFilter) 476 assert_equal %w( ensure_login ), response.template.assigns["ran_filter"] 477 end 478 469 479 def test_running_collection_condition_filters 470 480 assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"] 471 481 assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"] … … 499 509 assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"] 500 510 end 501 511 502 def test_bad_filter503 bad_filter_controller = Class.new(ActionController::Base)504 assert_raises(ActionController::ActionControllerError) do505 bad_filter_controller.before_filter 2506 end507 end508 509 512 def test_around_filter 510 513 controller = test_process(AroundFilterController) 511 514 assert controller.template.assigns["before_ran"] … … 746 749 assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size 747 750 end 748 751 749 def test_wrong_filter_type750 assert_raise ArgumentError do751 Class.new PostsController do752 around_filter lambda { yield }753 end754 end755 end756 757 752 def test_base 758 753 controller = PostsController 759 754 assert_nothing_raised { test_process(controller,'no_raise') } -
a/activerecord/lib/active_record/validations.rb
old new 282 282 283 283 base.send :include, ActiveSupport::Callbacks 284 284 285 # TODO: Use helper ActiveSupport::Callbacks#define_callbacks instead 286 %w( validate validate_on_create validate_on_update ).each do |validation_method| 285 VALIDATIONS.each do |validation_method| 287 286 base.class_eval <<-"end_eval" 288 287 def self.#{validation_method}(*methods, &block) 289 options = methods.extract_options! 290 methods << block if block_given? 291 methods.map! { |method| Callback.new(:#{validation_method}, method, options) } 292 existing_methods = read_inheritable_attribute(:#{validation_method}) || [] 293 write_inheritable_attribute(:#{validation_method}, existing_methods | methods) 288 methods = CallbackChain.build(:#{validation_method}, *methods, &block) 289 self.#{validation_method}_callback_chain.replace(#{validation_method}_callback_chain | methods) 294 290 end 295 291 296 292 def self.#{validation_method}_callback_chain 297 read_inheritable_attribute(:#{validation_method}) || [] 293 if chain = read_inheritable_attribute(:#{validation_method}) 294 return chain 295 else 296 write_inheritable_attribute(:#{validation_method}, CallbackChain.new) 297 return #{validation_method}_callback_chain 298 end 298 299 end 299 300 end_eval 300 301 end -
a/activesupport/lib/active_support/callbacks.rb
old new 76 76 # - save 77 77 # saved 78 78 module Callbacks 79 class Callback 80 def self.run(callbacks, object, options = {}, &terminator) 81 enumerator = options[:enumerator] || :each 79 class CallbackChain < Array 80 def self.build(kind, *methods, &block) 81 methods, options = extract_options(*methods, &block) 82 methods.map! { |method| Callback.new(kind, method, options) } 83 new(methods) 84 end 85 86 def run(object, options = {}, &terminator) 87 enumerator = options[:enumerator] || :each 82 88 83 89 unless block_given? 84 callbacks.send(enumerator) { |callback| callback.call(object) }90 send(enumerator) { |callback| callback.call(object) } 85 91 else 86 callbacks.send(enumerator) do |callback|92 send(enumerator) do |callback| 87 93 result = callback.call(object) 88 94 break result if terminator.call(result, object) 89 95 end 90 96 end 91 97 end 92 98 99 def find_callback(callback, &block) 100 select { |c| c == callback && (!block_given? || yield(c)) }.first 101 end 102 103 def replace_or_append_callback(callback) 104 if found_callback = find_callback(callback) 105 index = index(found_callback) 106 self[index] = callback 107 else 108 self << callback 109 end 110 end 111 112 private 113 def self.extract_options(*methods, &block) 114 methods.flatten! 115 options = methods.extract_options! 116 methods << block if block_given? 117 return methods, options 118 end 119 120 def extract_options(*methods, &block) 121 self.class.extract_options(*methods, &block) 122 end 123 end 124 125 class Callback 93 126 attr_reader :kind, :method, :identifier, :options 94 127 95 128 def initialize(kind, method, options = {}) … … 99 132 @options = options 100 133 end 101 134 102 def call(object) 103 evaluate_method(method, object) if should_run_callback?(object) 135 def ==(other) 136 case other 137 when Callback 138 (self.identifier && self.identifier == other.identifier) || self.method == other.method 139 else 140 (self.identifier && self.identifier == other) || self.method == other 141 end 142 end 143 144 def eql?(other) 145 self == other 146 end 147 148 def dup 149 self.class.new(@kind, @method, @options.dup) 150 end 151 152 def call(object, &block) 153 evaluate_method(method, object, &block) if should_run_callback?(object) 154 rescue LocalJumpError 155 raise ArgumentError, 156 "Cannot yield from a Proc type filter. The Proc must take two " + 157 "arguments and execute #call on the second argument." 104 158 end 105 159 106 160 private 107 def evaluate_method(method, object )161 def evaluate_method(method, object, &block) 108 162 case method 109 163 when Symbol 110 object.send(method )164 object.send(method, &block) 111 165 when String 112 166 eval(method, object.instance_eval { binding }) 113 167 when Proc, Method 114 method.call(object) 168 case method.arity 169 when -1, 1 170 method.call(object, &block) 171 when 2 172 method.call(object, block) 173 else 174 raise ArgumentError, 'Callback blocks must take one or two arguments.' 175 end 115 176 else 116 177 if method.respond_to?(kind) 117 method.send(kind, object )178 method.send(kind, object, &block) 118 179 else 119 180 raise ArgumentError, 120 181 "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + … … 143 204 callbacks.each do |callback| 144 205 class_eval <<-"end_eval" 145 206 def self.#{callback