天天看點

Rails源碼研究之ActionController:七,filters

我們上次看過了ActiveRecord的callbacks,這次看看ActionController的filters

[b]1,filter繼承[/b]

先執行父類中的filter,再執行子類中的filter,如果父類中的filter傳回false,則不執行子類中後續的filter

[b]2,filter類型[/b]

1)method reference(symbol)

[code]

class BankController < ActionController::Base

before_filter :audit

end

[/code]

2)external class

[code]

class OutputCompressionFilter

def self.filter(controller)

controller.response.body = compress(controller.response.body)

end

end

class NewspaperController < ActionController::Base

after_filter OutputCompressionFilter

end

[/code]

3)inline method(proc)

[code]

class WeblogController < ActionController::Base

before_filter { |controller| false if controller.params["stop_action"] }

end

[/code]

[b]3,filter鍊的執行順序[/b]

可以使用prepend_before_filter、prepend_after_filter和prepend_around_filter來讓某些filter最先執行

[code]

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

[/code]

現在filter的執行順序為:ensure_items_in_cart -> :ensure_items_in_stock -> :verify_open_shop

[b]4,Around filters[/b]

[code]

class SomeController < ActionController::Base

around_filter :catch_exceptions

private

def catch_exceptions

yield

rescue => exception

logger.debug "Caught exception! #{exception}"

raise

end

end

[/code]

[b]5,filter鍊skipping[/b]

[code]

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

[/code]

[b]6,filter conditions[/b]

[code]

class Journal < ActionController::Base

before_filter :authorize, : only => [:edit, :delete]

around_filter :catch_exceptions, :except => [:foo, :bar]

end

[/code]

[b]7,filter chain halting[/b]

對于如下filter定義:

[code]

class SomeController < ActionController::Base

before_filter :be

around_filter :ar

after_filter :af

end

[/code]

執行順序為:

[code]

# ...

# . \

# . #around (code before yield)

# . . \

# . . #before (actual filter code is run)

# . . . \

# . . . execute controller action

# . . . /

# . . ...

# . . /

# . #around (code after yield)

# . /

# #after (actual filter code is run)

[/code]

如果#before傳回false,則第二個#around和#after仍會執行

源代碼檔案為filters.rb:

[code]

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

[/code]

代碼一目了然,十有八九能猜到,使用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等