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

Changeset 6649

Show
Ignore:
Timestamp:
05/02/07 13:22:38 (1 year ago)
Author:
rick
Message:

Replace the current block/continuation filter chain handling by an implementation based on a simple loop. #8226 [Stefan Kaes]

Files:

Legend:

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

    r6648 r6649  
    11*SVN* 
     2 
     3* Replace the current block/continuation filter chain handling by an implementation based on a simple loop.  #8226 [Stefan Kaes] 
    24 
    35* Update UrlWriter to accept :anchor parameter. Closes #6771. [octopod] 
  • trunk/actionpack/lib/action_controller/filters.rb

    r6396 r6649  
    264264      # that run _after_ actions on this controller are performed. 
    265265      def append_after_filter(*filters, &block) 
    266         prepend_filter_to_chain(filters, :after, &block) 
     266        append_filter_to_chain(filters, :after, &block) 
    267267      end 
    268268 
     
    270270      # that run _after_ actions on this controller are performed. 
    271271      def prepend_after_filter(*filters, &block) 
    272         append_filter_to_chain(filters, :after, &block) 
     272        prepend_filter_to_chain(filters, :after, &block) 
    273273      end 
    274274 
     
    363363      # Returns a mapping between filters and the actions that may run them. 
    364364      def included_actions #:nodoc: 
    365         read_inheritable_attribute("included_actions") || {} 
     365        @included_actions ||= read_inheritable_attribute("included_actions") || {} 
    366366      end 
    367367 
    368368      # Returns a mapping between filters and actions that may not run them. 
    369369      def excluded_actions #:nodoc: 
    370         read_inheritable_attribute("excluded_actions") || {} 
     370        @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {} 
    371371      end 
    372372 
     
    382382      # Returns true if the filter is excluded from the given action 
    383383      def filter_excluded_from_action?(filter,action) #:nodoc: 
    384         if (ia = included_actions[filter]) && !ia.empty? 
     384        case 
     385        when ia = included_actions[filter] 
    385386          !ia.include?(action) 
    386         else 
    387           (excluded_actions[filter] || []).include?(action) 
     387        when ea = excluded_actions[filter] 
     388          ea.include?(action) 
    388389        end 
    389390      end 
     
    398399        end 
    399400 
     401        def type 
     402          :around 
     403        end 
     404 
    400405        def before? 
    401           fals
     406          type == :befor
    402407        end 
    403408 
    404409        def after? 
    405           false 
     410          type == :after 
    406411        end 
    407412 
    408413        def around? 
    409           true 
     414          type == :around 
     415        end 
     416 
     417        def run(controller) 
     418          raise ActionControllerError, 'No filter type: Nothing to do here.' 
    410419        end 
    411420 
    412421        def call(controller, &block) 
    413           raise(ActionControllerError, 'No filter type: Nothing to do here.'
     422          run(controller
    414423        end 
    415424      end 
     
    421430          @filter.filter 
    422431        end 
    423  
    424         def around? 
    425           false 
    426         end 
    427432      end 
    428433 
    429434      class BeforeFilterProxy < FilterProxy #:nodoc: 
    430         def before? 
    431           true 
    432         end 
    433  
    434         def call(controller, &block) 
    435           if false == @filter.call(controller) # must only stop if equal to false. only filters returning false are halted. 
    436             controller.halt_filter_chain(@filter, :returned_false) 
    437           else 
    438             yield 
    439           end 
     435        def type 
     436          :before 
     437        end 
     438 
     439        def run(controller) 
     440          # only filters returning false are halted. 
     441          if false == @filter.call(controller) 
     442            controller.halt_filter_chain(@filter) 
     443          end 
     444        end 
     445 
     446        def call(controller) 
     447          yield unless run(controller) 
    440448        end 
    441449      end 
    442450 
    443451      class AfterFilterProxy < FilterProxy #:nodoc: 
    444         def after? 
    445           true 
    446         end 
    447  
    448         def call(controller, &block) 
     452        def type 
     453          :after 
     454        end 
     455 
     456        def run(controller) 
     457          @filter.call(controller) 
     458        end 
     459 
     460        def call(controller) 
    449461          yield 
    450           @filter.call(controller) 
     462          run(controller) 
    451463        end 
    452464      end 
     
    486498      end 
    487499 
     500      class ClassBeforeFilter < Filter #:nodoc: 
     501        def call(controller, &block) 
     502          @filter.before(controller) 
     503        end 
     504      end 
     505 
     506      class ClassAfterFilter < Filter #:nodoc: 
     507        def call(controller, &block) 
     508          @filter.after(controller) 
     509        end 
     510      end 
     511 
    488512      protected 
    489         def append_filter_to_chain(filters, position = :around, &block) 
    490           write_inheritable_array('filter_chain', create_filters(filters, position, &block) ) 
    491         end 
    492  
    493         def prepend_filter_to_chain(filters, position = :around, &block) 
    494           write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain) 
    495         end 
    496  
    497         def create_filters(filters, position, &block) #:nodoc: 
     513        def append_filter_to_chain(filters, filter_type = :around, &block) 
     514          pos = find_filter_append_position(filters, filter_type) 
     515          update_filter_chain(filters, filter_type, pos, &block) 
     516        end 
     517 
     518        def prepend_filter_to_chain(filters, filter_type = :around, &block) 
     519          pos = find_filter_prepend_position(filters, filter_type) 
     520          update_filter_chain(filters, filter_type, pos, &block) 
     521        end 
     522 
     523        def update_filter_chain(filters, filter_type, pos, &block) 
     524          new_filters = create_filters(filters, filter_type, &block) 
     525          new_chain = filter_chain.insert(pos, new_filters).flatten 
     526          write_inheritable_attribute('filter_chain', new_chain) 
     527        end 
     528 
     529        def find_filter_append_position(filters, filter_type) 
     530          unless filter_type == :after 
     531            filter_chain.each_with_index do |f,i| 
     532              return i if f.after? 
     533            end 
     534          end 
     535          return -1 
     536        end 
     537 
     538        def find_filter_prepend_position(filters, filter_type) 
     539          if filter_type == :after 
     540            filter_chain.each_with_index do |f,i| 
     541              return i if f.after? 
     542            end 
     543          end 
     544          return 0 
     545        end 
     546 
     547        def create_filters(filters, filter_type, &block) #:nodoc: 
    498548          filters, conditions = extract_conditions(filters, &block) 
    499           filters.map! { |filter| find_or_create_filter(filter,position) } 
     549          filters.map! { |filter| find_or_create_filter(filter, filter_type) } 
    500550          update_conditions(filters, conditions) 
    501551          filters 
    502552        end 
    503553 
    504         def find_or_create_filter(filter,position
    505           if found_filter = find_filter(filter) { |f| f.send("#{position}?")
     554        def find_or_create_filter(filter, filter_type
     555          if found_filter = find_filter(filter) { |f| f.type == filter_type
    506556            found_filter 
    507557          else 
    508             f = class_for_filter(filter).new(filter) 
     558            f = class_for_filter(filter, filter_type).new(filter) 
    509559            # apply proxy to filter if necessary 
    510             case position 
     560            case filter_type 
    511561            when :before 
    512562              BeforeFilterProxy.new(f) 
     
    521571        # The determination of the filter type was once done at run time. 
    522572        # This method is here to extract as much logic from the filter run time as possible 
    523         def class_for_filter(filter) #:nodoc: 
     573        def class_for_filter(filter, filter_type) #:nodoc: 
    524574          case 
    525575          when filter.is_a?(Symbol) 
     
    535585          when filter.respond_to?(:filter) 
    536586            ClassFilter 
     587          when filter.respond_to?(:before) && filter_type == :before 
     588            ClassBeforeFilter 
     589          when filter.respond_to?(:after) && filter_type == :after 
     590            ClassAfterFilter 
    537591          else 
    538             raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.') 
     592            raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.') 
    539593          end 
    540594        end 
     
    551605          if conditions[:only] 
    552606            write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only])) 
    553           else 
    554             write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) if conditions[:except] 
     607          elsif conditions[:except] 
     608            write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) 
    555609          end 
    556610        end 
     
    577631        def remove_actions_from_included_actions!(filters,*actions) 
    578632          actions = actions.flatten.map(&:to_s) 
    579           updated_hash = filters.inject(included_actions) do |hash,filter| 
     633          updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter| 
    580634            ia = (hash[filter] || []) - actions 
    581             ia.blank? ? hash.delete(filter) : hash[filter] = ia 
     635            ia.empty? ? hash.delete(filter) : hash[filter] = ia 
    582636            hash 
    583637          end 
     
    596650          return filter unless filter_responds_to_before_and_after(filter) 
    597651          Proc.new do |controller, action| 
    598             unless filter.before(controller) == false 
     652            if filter.before(controller) == false 
     653              controller.send :halt_filter_chain, filter 
     654            else 
    599655              begin 
    600656                action.call 
     
    620676      end 
    621677 
    622       def call_filter(chain, index) 
    623         return (performed? || perform_action_without_filters) if index >= chain.size 
    624         filter = chain[index] 
    625         return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name) 
    626  
    627         halted = false 
    628         filter.call(self) do 
    629           halted = call_filter(chain, index.next) 
    630         end 
    631         halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted 
    632         halted 
    633       end 
    634  
    635       def halt_filter_chain(filter, reason) 
    636         if logger 
    637           case reason 
    638           when :no_yield 
    639             logger.info "Filter chain halted as [#{filter.inspect}] did not yield." 
    640           when :returned_false 
    641             logger.info "Filter chain halted as [#{filter.inspect}] returned false." 
    642           end 
    643         end 
     678      def skip_excluded_filters(chain, index) 
     679        while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name) 
     680          index = index.next 
     681        end 
     682        [filter, index] 
     683      end 
     684 
     685      def call_filters(chain, index, nesting) 
     686        # run before filters until we find an after filter or around filter 
     687        while true 
     688          filter, index = skip_excluded_filters(chain, index) 
     689          break unless filter 
     690          case filter.type 
     691          when :before 
     692            # invoke before filter 
     693            filter.run(self) 
     694            index = index.next 
     695            break if @before_filter_chain_aborted 
     696          when :around 
     697            filter.call(self) do 
     698              # all remaining before and around filters will be run in this call 
     699              index = call_filters(chain, index.next, nesting.next) 
     700            end 
     701            break 
     702          else 
     703            # no before or around filters left 
     704            break 
     705          end 
     706        end 
     707 
     708        aborted = @before_filter_chain_aborted 
     709        perform_action_without_filters unless performed? || aborted 
     710        return index if aborted || nesting != 0 
     711 
     712        # run after filters, if any 
     713        while filter = chain[index] 
     714          filter, index = skip_excluded_filters(chain, index) 
     715          case filter.type 
     716          when :after 
     717            filter.run(self) 
     718            index = index.next 
     719          else 
     720            raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" 
     721          end 
     722        end 
     723 
     724        index.next 
     725      end 
     726 
     727      def halt_filter_chain(filter) 
     728        logger.info "Filter chain halted as [#{filter.inspect}] returned false." if logger 
    644729        @before_filter_chain_aborted = true 
    645         return false 
     730        false 
    646731      end 
    647732 
     
    655740      private 
    656741        def perform_action_with_filters 
    657           call_filter(self.class.filter_chain, 0) 
     742          call_filters(self.class.filter_chain, 0, 0) 
    658743        end 
    659744 
  • trunk/actionpack/test/controller/filters_test.rb

    r5301 r6649  
    1515        @ran_filter << "ensure_login" 
    1616      end 
    17        
     17 
    1818      def clean_up 
    1919        @ran_after_filter ||= [] 
     
    6363      end 
    6464  end 
    65    
     65 
    6666  class ConditionalFilterController < ActionController::Base 
    6767    def show 
     
    8787        @ran_filter << "clean_up_tmp" 
    8888      end 
    89        
     89 
    9090      def rescue_action(e) raise(e) end 
    9191  end 
     
    9595  end 
    9696 
    97   class OnlyConditionSymController < ConditionalFilterController  
     97  class OnlyConditionSymController < ConditionalFilterController 
    9898    before_filter :ensure_login, :only => :show 
    9999  end 
     
    105105  class BeforeAndAfterConditionController < ConditionalFilterController 
    106106    before_filter :ensure_login, :only => :show 
    107     after_filter  :clean_up_tmp, :only => :show  
    108   end 
    109    
    110   class OnlyConditionProcController < ConditionalFilterController  
     107    after_filter  :clean_up_tmp, :only => :show 
     108  end 
     109 
     110  class OnlyConditionProcController < ConditionalFilterController 
    111111    before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true } 
    112112  end 
     
    146146    skip_before_filter :ensure_login, :only => [ :login ] 
    147147    skip_after_filter  :clean_up,     :only => [ :login ] 
    148      
     148 
    149149    before_filter :find_user, :only => [ :change_password ] 
    150150 
     
    156156      render :inline => "ran action" 
    157157    end 
    158      
     158 
    159159    protected 
    160160      def find_user 
     
    167167    before_filter :conditional_in_parent, :only => [:show, :another_action] 
    168168    after_filter  :conditional_in_parent, :only => [:show, :another_action] 
    169      
     169 
    170170    private 
    171        
     171 
    172172      def conditional_in_parent 
    173173        @ran_filter ||= [] 
     
    175175      end 
    176176  end 
    177    
     177 
    178178  class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController 
    179179    skip_before_filter :conditional_in_parent, :only => :another_action 
     
    198198    end 
    199199  end 
    200    
     200 
    201201  class AroundFilter 
    202202    def before(controller) 
     
    210210      controller.assigns["after_ran"] = true 
    211211      controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log 
    212     end     
     212    end 
    213213  end 
    214214 
     
    220220    def after(controller) 
    221221      controller.class.execution_log << " after appended aroundfilter " 
    222     end     
    223   end   
    224    
     222    end 
     223  end 
     224 
    225225  class AuditController < ActionController::Base 
    226226    before_filter(AuditFilter) 
    227      
     227 
    228228    def show 
    229229      render_text "hello" 
     
    235235  end 
    236236 
     237  class BeforeAfterClassFilterController < PrependingController 
     238    begin 
     239      filter = AroundFilter.new 
     240      before_filter filter 
     241      after_filter filter 
     242    end 
     243  end 
     244 
    237245  class MixedFilterController < PrependingController 
    238246    cattr_accessor :execution_log 
     
    248256    append_around_filter AppendedAroundFilter.new 
    249257  end 
    250    
     258 
    251259  class MixedSpecializationController < ActionController::Base 
    252260    class OutOfOrder < StandardError; end 
     
    293301    assert_equal [ ], ActionController::Base.before_filters 
    294302  end 
    295    
     303 
    296304  def test_prepending_filter 
    297305    assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters 
    298306  end 
    299    
     307 
    300308  def test_running_filters 
    301309    assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"] 
     
    305313    assert test_process(ProcController).template.assigns["ran_proc_filter"] 
    306314  end 
    307    
     315 
    308316  def test_running_filters_with_implicit_proc 
    309317    assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"] 
    310318  end 
    311    
     319 
    312320  def test_running_filters_with_class 
    313321    assert test_process(AuditController).template.assigns["was_audited"] 
     
    320328    assert response.template.assigns["ran_proc_filter1"] 
    321329    assert response.template.assigns["ran_proc_filter2"] 
    322      
     330 
    323331    response = test_process(AnomolousYetValidConditionController, "show_without_filter") 
    324332    assert_equal nil, response.template.assigns["ran_filter"] 
     
    374382  end 
    375383 
     384  def test_before_after_class_filter 
     385    controller = test_process(BeforeAfterClassFilterController) 
     386    assert controller.template.assigns["before_ran"] 
     387    assert controller.template.assigns["after_ran"] 
     388  end 
     389 
    376390  def test_having_properties_in_around_filter 
    377391    controller = test_process(AroundFilterController) 
     
    382396    controller = test_process(MixedFilterController) 
    383397    assert_equal " before aroundfilter  before procfilter  before appended aroundfilter " + 
    384                  " after appended aroundfilter  after aroundfilter  after procfilter ",  
     398                 " after appended aroundfilter  after aroundfilter  after procfilter ", 
    385399                 MixedFilterController.execution_log 
    386400  end 
    387    
     401 
    388402  def test_rendering_breaks_filtering_chain 
    389403    response = test_process(RenderingController)