Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7177

Show
Ignore:
Timestamp:
07/11/07 02:29:25 (1 year ago)
Author:
nzkoz
Message:

Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r7160 r7177  
    11*SVN* 
     2 
     3* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [skaes] 
     4 
     5  After filters will *no longer* be run if an around_filter fails to yield, users relying on 
     6  this behaviour are advised to put the code in question after a yield statement in an around filter. 
     7   
    28 
    39* Allow you to delete cookies with options. Closes #3685 [josh, Chris Wanstrath] 
  • trunk/actionpack/lib/action_controller/filters.rb

    r6810 r7177  
    215215    # 
    216216    # <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 
     217    # before a controller action is run. This is useful, for example, to deny 
    218218    # access to unauthenticated users or to redirect from http to https. 
    219219    # Simply return false from the filter or call render or redirect. 
     220    # After filters will not be executed if the filter chain is halted. 
    220221    # 
    221222    # Around filters halt the request unless the action block is called. 
     
    239240    #   .  #around (code after yield) 
    240241    #   . / 
    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. 
     242    #   #after (actual filter code is run, unless the around filter does not yield
     243    # 
     244    # If #around returns before yielding, #after will still not be run. The #before 
     245    # filter and controller action will not be run. If #before returns false, 
     246    # the second half of #around and will still run but #after and the 
     247    # action will not. If #around fails to yield, #after will not be run. 
    247248    module ClassMethods 
    248249      # The passed <tt>filters</tt> will be appended to the filter_chain and 
     
    440441          # only filters returning false are halted. 
    441442          if false == @filter.call(controller) 
    442             controller.halt_filter_chain(@filter) 
     443            controller.send :halt_filter_chain, @filter, :returned_false 
    443444          end 
    444445        end 
     
    529530        def find_filter_append_position(filters, filter_type) 
    530531          # appending an after filter puts it at the end of the call chain 
    531           # before and around filters goe before the first after filter in the chain 
     532          # before and around filters go before the first after filter in the chain 
    532533          unless filter_type == :after 
    533534            filter_chain.each_with_index do |f,i| 
     
    656657          Proc.new do |controller, action| 
    657658            if filter.before(controller) == false 
    658               controller.send :halt_filter_chain, filter 
     659              controller.send :halt_filter_chain, filter, :returned_false 
    659660            else 
    660661              begin 
     
    676677      end 
    677678 
    678       def filter_chain 
    679         self.class.filter_chain 
     679      protected 
     680 
     681      def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: 
     682        @before_filter_chain_aborted = false 
     683        process_without_filters(request, response, method, *arguments) 
     684      end 
     685 
     686      def perform_action_with_filters 
     687        call_filters(self.class.filter_chain, 0, 0) 
     688      end 
     689 
     690      private 
     691 
     692      def call_filters(chain, index, nesting) 
     693        index = run_before_filters(chain, index, nesting) 
     694        aborted = @before_filter_chain_aborted 
     695        perform_action_without_filters unless performed? || aborted 
     696        return index if nesting != 0 || aborted 
     697        run_after_filters(chain, index) 
    680698      end 
    681699 
     
    687705      end 
    688706 
    689       def call_filters(chain, index, nesting) 
    690         # run before filters until we find an after filter or around filter 
     707      def run_before_filters(chain, index, nesting) 
    691708        while chain[index] 
    692709          filter, index = skip_excluded_filters(chain, index) 
     
    694711          case filter.type 
    695712          when :before 
    696             # invoke before filter 
    697             filter.run(self) 
     713            filter.run(self)  # invoke before filter 
    698714            index = index.next 
    699715            break if @before_filter_chain_aborted 
    700716          when :around 
     717            yielded = false 
    701718            filter.call(self) do 
     719              yielded = true 
    702720              # all remaining before and around filters will be run in this call 
    703721              index = call_filters(chain, index.next, nesting.next) 
    704722            end 
     723            halt_filter_chain(filter, :did_not_yield) unless yielded 
    705724            break 
    706725          else 
    707             # no before or around filters left 
    708             break 
    709           end 
    710         end 
    711  
    712         aborted = @before_filter_chain_aborted 
    713         perform_action_without_filters unless performed? || aborted 
    714         return index if aborted || nesting != 0 
    715  
    716         # if an around filter catches an exception during rendering and handles it, e.g. 
    717         # by rendering an error page, we might have left over around filters in the filter 
    718         # chain. so skip over these. 
    719         index = index.next while (filter = chain[index]) && filter.type == :around 
    720  
    721         # run after filters, if any 
     726            break  # no before or around filters left 
     727          end 
     728        end 
     729        index 
     730      end 
     731 
     732      def run_after_filters(chain, index) 
     733        seen_after_filter = false 
    722734        while chain[index] 
    723735          filter, index = skip_excluded_filters(chain, index) 
    724           break unless filter 
     736          break unless filter # end of call chain reached 
    725737          case filter.type 
    726738          when :after 
    727             filter.run(self) 
    728             index = index.next 
     739            seen_after_filter = true 
     740            filter.run(self)  # invoke after filter 
    729741          else 
    730             raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" 
    731           end 
    732         end 
    733  
     742            # implementation error or someone has mucked with the filter chain 
     743            raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter 
     744          end 
     745          index = index.next 
     746        end 
    734747        index.next 
    735748      end 
    736749 
    737       def halt_filter_chain(filter) 
    738         logger.info "Filter chain halted as [#{filter.inspect}] returned false." if logger 
     750      def halt_filter_chain(filter, reason) 
    739751        @before_filter_chain_aborted = true 
     752        logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger 
    740753        false 
    741754      end 
    742  
    743       protected 
    744  
    745         def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: 
    746           @before_filter_chain_aborted = false 
    747           process_without_filters(request, response, method, *arguments) 
    748         end 
    749  
    750       private 
    751         def perform_action_with_filters 
    752           call_filters(self.class.filter_chain, 0, 0) 
    753         end 
    754  
    755755    end 
    756756  end 
  • trunk/actionpack/test/controller/filters_test.rb

    r6810 r7177  
    325325    end 
    326326  end 
    327    
     327 
    328328  class ErrorToRescue < Exception; end 
    329    
     329 
    330330  class RescuingAroundFilterWithBlock 
    331331    def filter(controller) 
     
    337337    end 
    338338  end 
    339    
     339 
    340340  class RescuedController < ActionController::Base 
    341341    around_filter RescuingAroundFilterWithBlock.new 
    342      
     342 
    343343    def show 
    344344      raise ErrorToRescue.new("Something made the bad noise.") 
    345345    end 
    346      
     346 
    347347  private 
    348348    def rescue_action(exception) 
    349349      raise exception 
    350350    end 
     351  end 
     352 
     353  class NonYieldingAroundFilterController < ActionController::Base 
     354 
     355    before_filter :filter_one 
     356    around_filter :non_yielding_filter 
     357    before_filter :filter_two 
     358    after_filter :filter_three 
     359 
     360    def index 
     361      render :inline => "index" 
     362    end 
     363 
     364    #make sure the controller complains 
     365    def rescue_action(e); raise e; end 
     366 
     367    private 
     368 
     369      def filter_one 
     370        @filters  ||= [] 
     371        @filters  << "filter_one" 
     372      end 
     373 
     374      def filter_two 
     375        @filters  << "filter_two" 
     376      end 
     377 
     378      def non_yielding_filter 
     379        @filters  << "zomg it didn't yield" 
     380        @filter_return_value 
     381      end 
     382 
     383      def filter_three 
     384        @filters  << "filter_three" 
     385      end 
     386 
     387  end 
     388 
     389  def test_non_yielding_around_filters_not_returning_false_do_not_raise 
     390    controller = NonYieldingAroundFilterController.new 
     391    controller.instance_variable_set "@filter_return_value", true 
     392    assert_nothing_raised do 
     393      test_process(controller, "index") 
     394    end 
     395  end 
     396 
     397  def test_non_yielding_around_filters_returning_false_do_not_raise 
     398    controller = NonYieldingAroundFilterController.new 
     399    controller.instance_variable_set "@filter_return_value", false 
     400    assert_nothing_raised do 
     401      test_process(controller, "index") 
     402    end 
     403  end 
     404 
     405  def test_after_filters_are_not_run_if_around_filter_returns_false 
     406    controller = NonYieldingAroundFilterController.new 
     407    controller.instance_variable_set "@filter_return_value", false 
     408    test_process(controller, "index") 
     409    assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters'] 
     410  end 
     411 
     412  def test_after_filters_are_not_run_if_around_filter_does_not_yield 
     413    controller = NonYieldingAroundFilterController.new 
     414    controller.instance_variable_set "@filter_return_value", true 
     415    test_process(controller, "index") 
     416    assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters'] 
    351417  end 
    352418 
     
    517583    assert_equal nil, test_process(ChangingTheRequirementsController, "go_wild").template.assigns['ran_filter'] 
    518584  end 
    519    
     585 
    520586  def test_a_rescuing_around_filter 
    521587    response = nil 
     
    523589      response = test_process(RescuedController) 
    524590    end 
    525      
     591 
    526592    assert response.success? 
    527593    assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)