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

Ticket #8891: fix_for_around_filters_returning_false.3.patch

File fix_for_around_filters_returning_false.3.patch, 10.5 kB (added by skaes, 2 years ago)

same as .2 with corrected docs

  • actionpack/lib/action_controller/filters.rb

    old new  
    214214    # == Filter Chain Halting 
    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 no be executed if the filter chain is halted. 
    220221    # 
    221222    # Around filters halt the request unless the action block is called. 
    222223    # Given these filters 
     
    238239    #   .  .  / 
    239240    #   .  #around (code after yield) 
    240241    #   . / 
    241     #   #after (actual filter code is run
     242    #   #after (actual filter code is run, unless around filter returns false or does not yield
    242243    # 
    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. 
     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 returns false, #after will not be run. 
    247248    module ClassMethods 
    248249      # The passed <tt>filters</tt> will be appended to the filter_chain and 
    249250      # will execute before the action on this controller is performed. 
     
    439440        def run(controller) 
    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 
    445446 
     
    528529 
    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| 
    534535              return i if f.after? 
     
    655656          return filter unless filter_responds_to_before_and_after(filter) 
    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 
    661662                action.call 
     
    675676        end 
    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) 
    680684      end 
    681685 
     686      private 
     687 
     688      def perform_action_with_filters 
     689        call_filters(self.class.filter_chain, 0, 0) 
     690      end 
     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) 
     698      end 
     699 
    682700      def skip_excluded_filters(chain, index) 
    683701        while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name) 
    684702          index = index.next 
     
    686704        [filter, index] 
    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) 
    693710          break unless filter # end of call chain reached 
    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 
     726            break  # no before or around filters left 
    709727          end 
    710728        end 
     729        index 
     730      end 
    711731 
    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 
     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!" 
     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 
    731744          end 
     745          index = index.next 
    732746        end 
    733  
    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 
    757757end 
  • actionpack/test/controller/filters_test.rb

    old new  
    324324      render :text => 'hello' 
    325325    end 
    326326  end 
    327    
     327 
    328328  class ErrorToRescue < Exception; end 
    329    
     329 
    330330  class RescuingAroundFilterWithBlock 
    331331    def filter(controller) 
    332332      begin 
     
    336336      end 
    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 
    351351  end 
    352352 
     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'] 
     417  end 
     418 
    353419  def test_empty_filter_chain 
    354420    assert_equal 0, EmptyFilterChainController.filter_chain.size 
    355421    assert test_process(EmptyFilterChainController).template.assigns['action_executed'] 
     
    516582  def test_changing_the_requirements 
    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 
    522588    assert_nothing_raised do 
    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) 
    528594  end