原创作者: hideto   阅读:2030次   评论:0条   更新时间:2011-05-26    
我们上次看过了ActiveRecord的callbacks,这次看看ActionController的filters
1,filter继承
先执行父类中的filter,再执行子类中的filter,如果父类中的filter返回false,则不执行子类中后续的filter

2,filter类型
1)method reference(symbol)
class BankController < ActionController::Base
  before_filter :audit
end

2)external class
class OutputCompressionFilter
  def self.filter(controller)
    controller.response.body = compress(controller.response.body)
  end
end

class NewspaperController < ActionController::Base
  after_filter OutputCompressionFilter
end

3)inline method(proc)
class WeblogController < ActionController::Base
  before_filter { |controller| false if controller.params["stop_action"] }
end


3,filter链的执行顺序
可以使用prepend_before_filter、prepend_after_filter和prepend_around_filter来让某些filter最先执行
class ShoppingController < ActionController::Base
  before_filter :verify_open_shop
end

class CheckoutController < ShoppingController
  prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
end

现在filter的执行顺序为:ensure_items_in_cart -> :ensure_items_in_stock -> :verify_open_shop

4,Around filters
class SomeController < ActionController::Base
  around_filter :catch_exceptions

  private
    def catch_exceptions
      yield
    rescue => exception
      logger.debug "Caught exception! #{exception}"
      raise
    end
end


5,filter链skipping
class ApplicationController < ActionController::Base
  before_filter :authenticate
  around_filter :catch_exceptions
end

class SignupController < ApplicationController
  # Skip :authenticate, run :catch_exceptions.
  skip_before_filter :authenticate
end


6,filter conditions
class Journal < ActionController::Base
  before_filter :authorize, : only => [:edit, :delete]
  around_filter :catch_exceptions, :except => [:foo, :bar]
end


7,filter chain halting
对于如下filter定义:
class SomeController < ActionController::Base
  before_filter :be
  around_filter :ar
  after_filter  :af
end

执行顺序为:
#   ...
#   . \
#   .  #around (code before yield)
#   .  .  \
#   .  .  #before (actual filter code is run)
#   .  .  .  \
#   .  .  .  execute controller action
#   .  .  .  /
#   .  .  ...
#   .  .  /
#   .  #around (code after yield)
#   . /
#   #after (actual filter code is run)

如果#before返回false,则第二个#around和#after仍会执行

源代码文件为filters.rb:
module ActionController
  module Filters
    def self.included(base)
      base.extend(ClassMethods)
      base.send(:include, ActionController::Filters::InstanceMethods)
    end

    module ClassMethods

      def append_before_filter(*filters, &block)
        append_filter_to_chain(filters, :before, &block)
      end

      alias :before_filter :append_before_filter

      def append_after_filter(*filters, &block)
        prepend_filter_to_chain(filters, :after, &block)
      end

      alias :after_filter :append_after_filter

      def prepend_before_filter(*filters, &block)
        prepend_filter_to_chain(filters, :before, &block)
      end

      def prepend_after_filter(*filters, &block)
        append_filter_to_chain(filters, :after, &block)
      end

      def append_around_filter(*filters, &block)
        filters, conditions = extract_conditions(filters, &block)
        filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
          append_filter_to_chain([filter, conditions])
        end
      end

      alias :around_filter :append_around_filter

      def prepend_around_filter(*filters, &block)
        filters, conditions = extract_conditions(filters, &block)
        filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
          prepend_filter_to_chain([filter, conditions])
        end
      end

      def skip_before_filter(*filters)
        skip_filter_in_chain(*filters, &:before?)
      end

      def skip_after_filter(*filters)
        skip_filter_in_chain(*filters, &:after?)
      end

      def skip_filter(*filters)
        skip_filter_in_chain(*filters)
      end

      class Filter
        def call(controller, &block)
          raise(ActionControllerError, 'No filter type: Nothing to do here.')
        end
      end

      class FilterProxy < Filter
        def filter
          @filter.filter
        end

        def around?
          false
        end
      end

      class BeforeFilterProxy < FilterProxy
        def before?
          true
        end

        def call(controller, &block)
          if false == @filter.call(controller)
            controller.halt_filter_chain(@filter, :returned_false)
          else
            yield
          end
        end
      end

      class AfterFilterProxy < FilterProxy
        def after?
          true
        end

        def call(controller, &block)
          yield
          @filter.call(controller)
        end
      end

      class SymbolFilter < Filter
        def call(controller, &block)
          controller.send(@filter, &block)
        end
      end

      class ProcFilter < Filter
        def call(controller)
          @filter.call(controller)
        rescue LocalJumpError
          raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
        end
      end

      class ProcWithCallFilter < Filter
        def call(controller, &block)
          @filter.call(controller, block)
        rescue LocalJumpError
          raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.')
        end
      end

      class MethodFilter < Filter
        def call(controller, &block)
          @filter.call(controller, &block)
        end
      end

      class ClassFilter < Filter
        def call(controller, &block)
          @filter.filter(controller, &block)
        end
      end

      protected

        def append_filter_to_chain(filters, position = :around, &block)
          write_inheritable_array('filter_chain', create_filters(filters, position, &block) )
        end

        def prepend_filter_to_chain(filters, position = :around, &block)
          write_inheritable_attribute('filter_chain', create_filters(filters, position, &block) + filter_chain)
        end

        def create_filters(filters, position, &block)
          filters, conditions = extract_conditions(filters, &block)
          filters.map! { |filter| find_or_create_filter(filter,position) }
          update_conditions(filters, conditions)
          filters
        end

        def find_or_create_filter(filter,position)
          if found_filter = find_filter(filter) { |f| f.send("#{position}?") }
            found_filter
          else
            f = class_for_filter(filter).new(filter)
            case position
            when :before
              BeforeFilterProxy.new(f)
            when :after
              AfterFilterProxy.new(f)
            else
              f
            end
          end
        end

        def class_for_filter(filter)
          case
          when filter.is_a?(Symbol)
            SymbolFilter
          when filter.respond_to?(:call)
            if filter.is_a?(Method)
              MethodFilter
            elsif filter.arity == 1
              ProcFilter
            else
              ProcWithCallFilter
            end
          when filter.respond_to?(:filter)
            ClassFilter
          else
            raise(ActionControllerError, 'A filters must be a Symbol, Proc, Method, or object responding to filter.')
          end
        end

        def skip_filter_in_chain(*filters, &test)
          filters, conditions = extract_conditions(filters)
          filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) }
          filters.compact!

          if conditions.empty?
            delete_filters_in_chain(filters)
          else
            remove_actions_from_included_actions!(filters,conditions[: only] || [])
            conditions[: only], conditions[:except] = conditions[:except], conditions[: only]
            update_conditions(filters,conditions)
          end
        end

        def proxy_before_and_after_filter(filter)
          return filter unless filter_responds_to_before_and_after(filter)
          Proc.new do |controller, action|
            unless filter.before(controller) == false
              begin
                action.call
              ensure
                filter.after(controller)
              end
            end
          end
        end
    end

    module InstanceMethods

      def self.included(base)
        base.class_eval do
          alias_method_chain :perform_action, :filters
          alias_method_chain :process, :filters
          alias_method_chain :process_cleanup, :filters
        end
      end

      def perform_action_with_filters
        call_filter(self.class.filter_chain, 0)
      end

      def process_with_filters(request, response, method = :perform_action, *arguments)
        @before_filter_chain_aborted = false
        process_without_filters(request, response, method, *arguments)
      end

      def filter_chain
        self.class.filter_chain
      end

      def call_filter(chain, index)
        return (performed? || perform_action_without_filters) if index >= chain.size
        filter = chain[index]
        return call_filter(chain, index.next) if self.class.filter_excluded_from_action?(filter,action_name)

        halted = false
        filter.call(self) do
          halted = call_filter(chain, index.next)
        end
        halt_filter_chain(filter.filter, :no_yield) if halted == false unless @before_filter_chain_aborted
        halted
      end

      def halt_filter_chain(filter, reason)
        if logger
          case reason
          when :no_yield
            logger.info "Filter chain halted as [#{filter.inspect}] did not yield."
          when :returned_false
            logger.info "Filter chain halted as [#{filter.inspect}] returned false."
          end
        end
        @before_filter_chain_aborted = true
        return false
      end
    end
  end
end

代码一目了然,十有八九能猜到,使用alias_method_chain给perform_action/process/process_cleanup加上filter
perform_action_with_filters -> call_filter -> filter.call -> BeforeFilterProxy.call || AfterFilterProxy.call || proxy_before_and_after_filter
其他则为一些增删改filter_chain的public方法,如before_filter、after_filter、around_filter、skip_filter等
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

文章信息

Global site tag (gtag.js) - Google Analytics