lib/active_support/callbacks.rb in activesupport-3.0.pre vs lib/active_support/callbacks.rb in activesupport-3.0.0.rc
- old
+ new
@@ -1,8 +1,10 @@
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/kernel/singleton_class'
module ActiveSupport
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
# before or after an alteration of the object state.
#
@@ -81,18 +83,22 @@
# saved
#
module Callbacks
extend Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
class Callback
@@_callback_sequence = 0
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass
+ attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
normalize_options!(options)
@@ -200,22 +206,21 @@
# yield self
# end
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
- txt, line = <<-RUBY_EVAL, __LINE__ + 1
- def #{name}(halted)
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{name}(halted)
#{@compiled_options[0] || "if true"} && !halted
#{@filter} do
yield self
end
else
yield self
end
end
RUBY_EVAL
- @klass.class_eval(txt, __FILE__, line)
"#{name}(halted) do"
end
end
end
@@ -309,13 +314,13 @@
end
end
def _normalize_legacy_filter(kind, filter)
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- filter.metaclass.class_eval(
- "def #{kind}(context, &block) filter(context, &block) end",
- __FILE__, __LINE__ - 1)
+ filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{kind}(context, &block) filter(context, &block) end
+ RUBY_EVAL
elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
def filter.around(context)
should_continue = before(context)
yield if should_continue
after(context)
@@ -365,16 +370,10 @@
method << "raise rescued_error if rescued_error" if config[:rescuable]
method << "halted ? false : (block_given? ? value : true)"
method.compact.join("\n")
end
-
- def clone(klass)
- chain = CallbackChain.new(@name, @config.dup)
- callbacks = map { |c| c.clone(chain, klass) }
- chain.push(*callbacks)
- end
end
module ClassMethods
# Make the run_callbacks :save method. The generated method takes
# a block that it'll yield to. It'll call the before and around filters
@@ -389,30 +388,28 @@
# key. See #define_callbacks for more information.
#
def __define_runner(symbol) #:nodoc:
body = send("_#{symbol}_callbacks").compile(nil)
- body, line = <<-RUBY_EVAL, __LINE__
- def _run_#{symbol}_callbacks(key = nil, &blk)
- if key
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
+ silence_warnings do
+ undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def _run_#{symbol}_callbacks(key = nil, &blk)
+ if key
+ name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
- unless respond_to?(name)
- self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
- end
+ unless respond_to?(name)
+ self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
+ end
- send(name, &blk)
- else
- #{body}
+ send(name, &blk)
+ else
+ #{body}
+ end
end
- end
- private :_run_#{symbol}_callbacks
- RUBY_EVAL
-
- silence_warnings do
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
- class_eval body, __FILE__, line
+ private :_run_#{symbol}_callbacks
+ RUBY_EVAL
end
end
# This is called the first time a callback is called with a particular
# key. It creates a new callback method for the key, calculating
@@ -433,14 +430,15 @@
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- chain = send("_#{name}_callbacks")
- yield chain, type, filters, options if block_given?
-
- __define_runner(name)
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
+ chain = target.send("_#{name}_callbacks")
+ yield chain, type, filters, options
+ target.__define_runner(name)
+ end
end
# Set callbacks for a previously defined callback.
#
# Syntax:
@@ -449,15 +447,17 @@
# set_callback :save, :around, lambda { |r| stuff; yield; stuff }
#
# Use skip_callback to skip any defined one.
#
# When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in ActionPack,
+ # are always the same for a given key. For instance, in Action Pack,
# we convert :only and :except conditions into per-key conditions.
#
# before_filter :authenticate, :except => "index"
+ #
# becomes
+ #
# dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
#
# Per-Key conditions are evaluated only once per use of a given key.
# In the case of the above example, you would do:
#
@@ -465,96 +465,125 @@
#
# In that case, each action_name would get its own compiled callback
# method that took into consideration the per_key conditions. This
# is a speed improvement for ActionPack.
#
- def set_callback(name, *filters, &block)
- __update_callbacks(name, filters, block) do |chain, type, filters, options|
- filters.map! do |filter|
- chain.delete_if {|c| c.matches?(type, filter) }
+ def set_callback(name, *filter_list, &block)
+ mapped = nil
+
+ __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
+ mapped ||= filters.map do |filter|
Callback.new(chain, filter, type, options.dup, self)
end
- options[:prepend] ? chain.unshift(*filters) : chain.push(*filters)
+ filters.each do |filter|
+ chain.delete_if {|c| c.matches?(type, filter) }
+ end
+
+ options[:prepend] ? chain.unshift(*mapped) : chain.push(*mapped)
end
end
# Skip a previously defined callback for a given type.
#
- def skip_callback(name, *filters, &block)
- __update_callbacks(name, filters, block) do |chain, type, filters, options|
- chain = send("_#{name}_callbacks=", chain.clone(self))
-
+ def skip_callback(name, *filter_list, &block)
+ __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- filter.recompile!(options, options[:per_key] || {})
- else
- chain.delete(filter)
+ new_filter = filter.clone(chain, self)
+ chain.insert(chain.index(filter), new_filter)
+ new_filter.recompile!(options, options[:per_key] || {})
end
+
+ chain.delete(filter)
end
end
end
# Reset callbacks for a given type.
#
def reset_callbacks(symbol)
- send("_#{symbol}_callbacks").clear
+ callbacks = send("_#{symbol}_callbacks")
+
+ ActiveSupport::DescendantsTracker.descendants(self).each do |target|
+ chain = target.send("_#{symbol}_callbacks")
+ callbacks.each { |c| chain.delete(c) }
+ target.__define_runner(symbol)
+ end
+
+ callbacks.clear
__define_runner(symbol)
end
- # Define callbacks types.
+ # Defines callbacks types:
#
- # ==== Example
- #
# define_callbacks :validate
#
- # ==== Options
+ # This macro accepts the following options:
#
# * <tt>:terminator</tt> - Indicates when a before filter is considered
# to be halted.
#
# define_callbacks :validate, :terminator => "result == false"
#
- # In the example above, if any before validate callbacks returns false,
- # other callbacks are not executed. Defaults to "false".
+ # In the example above, if any before validate callbacks returns +false+,
+ # other callbacks are not executed. Defaults to "false", meaning no value
+ # halts the chain.
#
# * <tt>:rescuable</tt> - By default, after filters are not executed if
- # the given block or an before_filter raises an error. Supply :rescuable => true
- # to change this behavior.
+ # the given block or a before filter raises an error. Set this option to
+ # true to change this behavior.
#
- # * <tt>:scope</tt> - Show which methods should be executed when a class
- # is giben as callback:
+ # * <tt>:scope</tt> - Indicates which methods should be executed when a class
+ # is given as callback. Defaults to <tt>[:kind]</tt>.
#
- # define_callbacks :filters, :scope => [ :kind ]
+ # class Audit
+ # def before(caller)
+ # puts 'Audit: before is called'
+ # end
#
- # When a class is given:
+ # def before_save(caller)
+ # puts 'Audit: before_save is called'
+ # end
+ # end
#
- # before_filter MyFilter
+ # class Account
+ # include ActiveSupport::Callbacks
#
- # It will call the type of the filter in the given class, which in this
- # case, is "before".
+ # define_callbacks :save
+ # set_callback :save, :before, Audit.new
#
- # If, for instance, you supply the given scope:
+ # def save
+ # run_callbacks :save do
+ # puts 'save in main'
+ # end
+ # end
+ # end
#
- # define_callbacks :validate, :scope => [ :kind, :name ]
+ # In the above case whenever you save an account the method <tt>Audit#before</tt> will
+ # be called. On the other hand
#
- # It will call "#{kind}_#{name}" in the given class. So "before_validate"
- # will be called in the class below:
+ # define_callbacks :save, :scope => [:kind, :name]
#
- # before_validate MyValidation
+ # would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
+ # <tt>"#{kind}_#{name}"</tt> on the given instance. In this case "kind" is "before" and
+ # "name" is "save".
#
- # Defaults to :kind.
+ # A declaration like
#
- def define_callbacks(*symbols)
- config = symbols.last.is_a?(Hash) ? symbols.pop : {}
- symbols.each do |symbol|
- extlib_inheritable_accessor("_#{symbol}_callbacks") do
- CallbackChain.new(symbol, config)
+ # define_callbacks :save, :scope => [:name]
+ #
+ # would call <tt>Audit#save</tt>.
+ #
+ def define_callbacks(*callbacks)
+ config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
+ callbacks.each do |callback|
+ extlib_inheritable_reader("_#{callback}_callbacks") do
+ CallbackChain.new(callback, config)
end
-
- __define_runner(symbol)
+ __define_runner(callback)
end
end
end
end
end