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等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值