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

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  
    2020      # existing callback. Passing an identifier is a suggested practice if the 
    2121      # code adding a preparation block may be reloaded. 
    2222      def to_prepare(identifier = nil, &block) 
    23         @prepare_dispatch_callbacks ||= [] 
     23        @prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new 
    2424        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) 
    3426      end 
    3527 
    3628      # If the block raises, send status code as a last-ditch response. 
  • a/actionpack/lib/action_controller/filters.rb

    old new  
    244244    # filter and controller action will not be run. If #before renders or redirects, 
    245245    # the second half of #around and will still run but #after and the 
    246246    # 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 
    247433    module ClassMethods 
    248434      # The passed <tt>filters</tt> will be appended to the filter_chain and 
    249435      # will execute before the action on this controller is performed. 
    250436      def append_before_filter(*filters, &block) 
    251         append_filter_to_chain(filters, :before, &block) 
     437        filter_chain.append_filter_to_chain(filters, :before, &block) 
    252438      end 
    253439 
    254440      # The passed <tt>filters</tt> will be prepended to the filter_chain and 
    255441      # will execute before the action on this controller is performed. 
    256442      def prepend_before_filter(*filters, &block) 
    257         prepend_filter_to_chain(filters, :before, &block) 
     443        filter_chain.prepend_filter_to_chain(filters, :before, &block) 
    258444      end 
    259445 
    260446      # Shorthand for append_before_filter since it's the most common. 
     
    263449      # The passed <tt>filters</tt> will be appended to the array of filters 
    264450      # that run _after_ actions on this controller are performed. 
    265451      def append_after_filter(*filters, &block) 
    266         append_filter_to_chain(filters, :after, &block) 
     452        filter_chain.append_filter_to_chain(filters, :after, &block) 
    267453      end 
    268454 
    269455      # The passed <tt>filters</tt> will be prepended to the array of filters 
    270456      # that run _after_ actions on this controller are performed. 
    271457      def prepend_after_filter(*filters, &block) 
    272         prepend_filter_to_chain(filters, :after, &block) 
     458        filter_chain.prepend_filter_to_chain(filters, :after, &block) 
    273459      end 
    274460 
    275461      # Shorthand for append_after_filter since it's the most common. 
    276462      alias :after_filter :append_after_filter 
    277463 
    278  
    279464      # If you append_around_filter A.new, B.new, the filter chain looks like 
    280465      # 
    281466      #   B#before 
     
    287472      # With around filters which yield to the action block, #before and #after 
    288473      # are the code before and after the yield. 
    289474      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) 
    294476      end 
    295477 
    296478      # If you prepend_around_filter A.new, B.new, the filter chain looks like: 
     
    304486      # With around filters which yield to the action block, #before and #after 
    305487      # are the code before and after the yield. 
    306488      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) 
    311490      end 
    312491 
    313492      # Shorthand for append_around_filter since it's the most common. 
     
    320499      # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 
    321500      # just like when you apply the filters. 
    322501      def skip_before_filter(*filters) 
    323         skip_filter_in_chain(*filters, &:before?) 
     502        filter_chain.skip_filter_in_chain(*filters, &:before?) 
    324503      end 
    325504 
    326505      # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference 
     
    330509      # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 
    331510      # just like when you apply the filters. 
    332511      def skip_after_filter(*filters) 
    333         skip_filter_in_chain(*filters, &:after?) 
     512        filter_chain.skip_filter_in_chain(*filters, &:after?) 
    334513      end 
    335514 
    336515      # Removes the specified filters from the filter chain. This only works for method reference (symbol) 
     
    340519      # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options, 
    341520      # just like when you apply the filters. 
    342521      def skip_filter(*filters) 
    343         skip_filter_in_chain(*filters) 
     522        filter_chain.skip_filter_in_chain(*filters) 
    344523      end 
    345524 
    346525      # Returns an array of Filter objects for this controller. 
    347526      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 
    349533      end 
    350534 
    351535      # Returns all the before filters for this class and all its ancestors. 
    352536      # This method returns the actual filter that was assigned in the controller to maintain existing functionality. 
    353537      def before_filters #:nodoc: 
    354         filter_chain.select(&:before?).map(&:filter
     538        filter_chain.select(&:before?).map(&:method
    355539      end 
    356540 
    357541      # Returns all the after filters for this class and all its ancestors. 
    358542      # This method returns the actual filter that was assigned in the controller to maintain existing functionality. 
    359543      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) 
    380545      end 
    381  
    382       # Returns true if the filter is excluded from the given action 
    383       def filter_excluded_from_action?(filter,action) #:nodoc: 
    384         case 
    385         when ia = included_actions[filter] 
    386           !ia.include?(action) 
    387         when ea = excluded_actions[filter] 
    388           ea.include?(action) 
    389         end 
    390       end 
    391  
    392       # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but 
    393       # contains no logic for calling the actual filters. 
    394       class Filter #:nodoc: 
    395         attr_reader :filter, :included_actions, :excluded_actions 
    396  
    397         def initialize(filter) 
    398           @filter = filter 
    399         end 
    400  
    401         def type 
    402           :around 
    403         end 
    404  
    405         def before? 
    406           type == :before 
    407         end 
    408  
    409         def after? 
    410           type == :after 
    411         end 
    412  
    413         def around? 
    414           type == :around 
    415         end 
    416  
    417         def run(controller) 
    418           raise ActionControllerError, 'No filter type: Nothing to do here.' 
    419         end 
    420  
    421         def call(controller, &block) 
    422           run(controller) 
    423         end 
    424       end 
    425  
    426       # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old 
    427       # before_filter and after_filter by moving the logic into the filter itself. 
    428       class FilterProxy < Filter #:nodoc: 
    429         def filter 
    430           @filter.filter 
    431         end 
    432       end 
    433  
    434       class BeforeFilterProxy < FilterProxy #:nodoc: 
    435         def type 
    436           :before 
    437         end 
    438  
    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           end 
    445         end 
    446  
    447         def call(controller) 
    448           yield unless run(controller) 
    449         end 
    450       end 
    451  
    452       class AfterFilterProxy < FilterProxy #:nodoc: 
    453         def type 
    454           :after 
    455         end 
    456  
    457         def run(controller) 
    458           @filter.call(controller) 
    459         end 
    460  
    461         def call(controller) 
    462           yield 
    463           run(controller) 
    464         end 
    465       end 
    466  
    467       class SymbolFilter < Filter #:nodoc: 
    468         def call(controller, &block) 
    469           controller.send!(@filter, &block) 
    470         end 
    471       end 
    472  
    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         end 
    479       end 
    480  
    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         end 
    487       end 
    488  
    489       class MethodFilter < Filter #:nodoc: 
    490         def call(controller, &block) 
    491           @filter.call(controller, &block) 
    492         end 
    493       end 
    494  
    495       class ClassFilter < Filter #:nodoc: 
    496         def call(controller, &block) 
    497           @filter.filter(controller, &block) 
    498         end 
    499       end 
    500  
    501       class ClassBeforeFilter < Filter #:nodoc: 
    502         def call(controller, &block) 
    503           @filter.before(controller) 
    504         end 
    505       end 
    506  
    507       class ClassAfterFilter < Filter #:nodoc: 
    508         def call(controller, &block) 
    509           @filter.after(controller) 
    510         end 
    511       end 
    512  
    513       protected 
    514         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         end 
    518  
    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         end 
    523  
    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).flatten 
    527           write_inheritable_attribute('filter_chain', new_chain) 
    528         end 
    529  
    530         def find_filter_append_position(filters, filter_type) 
    531           # appending an after filter puts it at the end of the call chain 
    532           # before and around filters go before the first after filter in the chain 
    533           unless filter_type == :after 
    534             filter_chain.each_with_index do |f,i| 
    535               return i if f.after? 
    536             end 
    537           end 
    538           return -1 
    539         end 
    540  
    541         def find_filter_prepend_position(filters, filter_type) 
    542           # prepending a before or around filter puts it at the front of the call chain 
    543           # after filters go before the first after filter in the chain 
    544           if filter_type == :after 
    545             filter_chain.each_with_index do |f,i| 
    546               return i if f.after? 
    547             end 
    548             return -1 
    549           end 
    550           return 0 
    551         end 
    552  
    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           filters 
    558         end 
    559  
    560         def find_or_create_filter(filter, filter_type) 
    561           if found_filter = find_filter(filter) { |f| f.type == filter_type } 
    562             found_filter 
    563           else 
    564             f = class_for_filter(filter, filter_type).new(filter) 
    565             # apply proxy to filter if necessary 
    566             case filter_type 
    567             when :before 
    568               BeforeFilterProxy.new(f) 
    569             when :after 
    570               AfterFilterProxy.new(f) 
    571             else 
    572               f 
    573             end 
    574           end 
    575         end 
    576  
    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 possible 
    579         def class_for_filter(filter, filter_type) #:nodoc: 
    580           case 
    581           when filter.is_a?(Symbol) 
    582             SymbolFilter 
    583           when filter.respond_to?(:call) 
    584             if filter.is_a?(Method) 
    585               MethodFilter 
    586             else 
    587               case filter.arity 
    588               when 1; ProcFilter 
    589               when 2; ProcWithCallFilter 
    590               else raise ArgumentError, 'Filter blocks must take one or two arguments.' 
    591               end 
    592             end 
    593           when filter.respond_to?(:filter) 
    594             ClassFilter 
    595           when filter.respond_to?(:before) && filter_type == :before 
    596             ClassBeforeFilter 
    597           when filter.respond_to?(:after) && filter_type == :after 
    598             ClassAfterFilter 
    599           else 
    600             raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.') 
    601           end 
    602         end 
    603  
    604         def extract_conditions(*filters, &block) #:nodoc: 
    605           filters.flatten! 
    606           conditions = filters.extract_options! 
    607           filters << block if block_given? 
    608           return filters, conditions 
    609         end 
    610  
    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           end 
    618         end 
    619  
    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         end 
    624  
    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           else 
    633             remove_actions_from_included_actions!(filters,conditions[:only] || []) 
    634             conditions[:only], conditions[:except] = conditions[:except], conditions[:only] 
    635             update_conditions(filters,conditions) 
    636           end 
    637         end 
    638  
    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] || []) - actions 
    643             ia.empty? ? hash.delete(filter) : hash[filter] = ia 
    644             hash 
    645           end 
    646           write_inheritable_attribute('included_actions', updated_hash) 
    647         end 
    648  
    649         def delete_filters_in_chain(filters) #:nodoc: 
    650           write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) }) 
    651         end 
    652  
    653         def filter_responds_to_before_and_after(filter) #:nodoc: 
    654           filter.respond_to?(:before) && filter.respond_to?(:after) 
    655         end 
    656  
    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             else 
    665               begin 
    666                 action.call 
    667               ensure 
    668                 filter.after(controller) 
    669               end 
    670             end 
    671           end 
    672         end 
    673546    end 
    674547 
    675548    module InstanceMethods # :nodoc: 
     
    681554      end 
    682555 
    683556      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 
    684561 
    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 
    693565 
    694566      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 
    695593 
    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 
    715595 
    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 
    728599            end 
    729  
    730             halt_filter_chain(filter, :did_not_yield) unless yielded 
    731  
    732             break 
    733           else 
    734             break  # no before or around filters left 
    735600          end 
     601 
     602          index 
    736603        end 
    737604 
    738         index 
    739       end 
     605        def run_after_filters(chain, index) 
     606          seen_after_filter = false 
    740607 
    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 
    743611 
    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 
    747620 
    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 
    755622          end 
    756623 
    757           index = index.next 
     624          index.next 
    758625        end 
    759626 
    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 
    767631    end 
    768632  end 
    769633end 
  • a/actionpack/test/controller/dispatcher_test.rb

    old new  
    1111    @output = StringIO.new 
    1212    ENV['REQUEST_METHOD'] = 'GET' 
    1313 
    14     Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", []
     14    Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new
    1515    @dispatcher = Dispatcher.new(@output) 
    1616  end 
    1717 
  • a/actionpack/test/controller/filters_test.rb

    old new  
    134134    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} 
    135135  end 
    136136 
     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 
    137142  class EmptyFilterChainController < TestController 
    138143    self.filter_chain.clear 
    139144    def show 
     
    466471    assert !response.template.assigns["ran_proc_filter2"] 
    467472  end 
    468473 
     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 
    469479  def test_running_collection_condition_filters 
    470480    assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"] 
    471481    assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"] 
     
    499509    assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"] 
    500510  end 
    501511 
    502   def test_bad_filter 
    503     bad_filter_controller = Class.new(ActionController::Base) 
    504     assert_raises(ActionController::ActionControllerError) do 
    505       bad_filter_controller.before_filter 2 
    506     end 
    507   end 
    508  
    509512  def test_around_filter 
    510513    controller = test_process(AroundFilterController) 
    511514    assert controller.template.assigns["before_ran"] 
     
    746749    assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size 
    747750  end 
    748751 
    749   def test_wrong_filter_type 
    750     assert_raise ArgumentError do 
    751       Class.new PostsController do 
    752         around_filter lambda { yield } 
    753       end 
    754     end 
    755   end 
    756  
    757752  def test_base 
    758753    controller = PostsController 
    759754    assert_nothing_raised { test_process(controller,'no_raise') } 
  • a/activerecord/lib/active_record/validations.rb

    old new  
    282282 
    283283      base.send :include, ActiveSupport::Callbacks 
    284284 
    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| 
    287286        base.class_eval <<-"end_eval" 
    288287          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) 
    294290          end 
    295291 
    296292          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 
    298299          end 
    299300        end_eval 
    300301      end 
  • a/activesupport/lib/active_support/callbacks.rb

    old new  
    7676  #   - save 
    7777  #   saved 
    7878  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 
    8288 
    8389        unless block_given? 
    84           callbacks.send(enumerator) { |callback| callback.call(object) } 
     90          send(enumerator) { |callback| callback.call(object) } 
    8591        else 
    86           callbacks.send(enumerator) do |callback| 
     92          send(enumerator) do |callback| 
    8793            result = callback.call(object) 
    8894            break result if terminator.call(result, object) 
    8995          end 
    9096        end 
    9197      end 
    9298 
     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 
    93126      attr_reader :kind, :method, :identifier, :options 
    94127 
    95128      def initialize(kind, method, options = {}) 
     
    99132        @options    = options 
    100133      end 
    101134 
    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." 
    104158      end 
    105159 
    106160      private 
    107         def evaluate_method(method, object
     161        def evaluate_method(method, object, &block
    108162          case method 
    109163            when Symbol 
    110               object.send(method
     164              object.send(method, &block
    111165            when String 
    112166              eval(method, object.instance_eval { binding }) 
    113167            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 
    115176            else 
    116177              if method.respond_to?(kind) 
    117                 method.send(kind, object
     178                method.send(kind, object, &block
    118179              else 
    119180                raise ArgumentError, 
    120181                  "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + 
     
    143204        callbacks.each do |callback| 
    144205          class_eval <<-"end_eval" 
    145206            def self.#{callback