lib/active_support/callbacks.rb in activesupport-3.0.0.beta4 vs lib/active_support/callbacks.rb in activesupport-3.0.pre
- old
+ new
@@ -1,9 +1,8 @@
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.
#
@@ -89,11 +88,11 @@
end
class Callback
@@_callback_sequence = 0
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
+ attr_accessor :chain, :filter, :kind, :options, :per_key, :klass
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
normalize_options!(options)
@@ -201,21 +200,22 @@
# yield self
# end
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
+ txt, line = <<-RUBY_EVAL, __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 +309,13 @@
end
end
def _normalize_legacy_filter(kind, filter)
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{kind}(context, &block) filter(context, &block) end
- RUBY_EVAL
+ filter.metaclass.class_eval(
+ "def #{kind}(context, &block) filter(context, &block) end",
+ __FILE__, __LINE__ - 1)
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,10 +365,16 @@
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
@@ -381,39 +387,32 @@
# The run_callbacks :save method can optionally take a key, which
# will be used to compile an optimized callback method for each
# key. See #define_callbacks for more information.
#
def __define_runner(symbol) #:nodoc:
- send("_update_#{symbol}_superclass_callbacks")
body = send("_#{symbol}_callbacks").compile(nil)
- 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)
- @_initialized_#{symbol}_callbacks ||= begin
- if self.class.send("_update_#{symbol}_superclass_callbacks")
- self.class.__define_runner(#{symbol.inspect})
- return _run_#{symbol}_callbacks(key, &blk)
- end
- true
- end
+ 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"
- 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
-
- send(name, &blk)
- else
- #{body}
+ unless respond_to?(name)
+ self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
end
+
+ send(name, &blk)
+ else
+ #{body}
end
- private :_run_#{symbol}_callbacks
- RUBY_EVAL
+ 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
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
@@ -430,12 +429,10 @@
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
#
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
- send("_update_#{name}_superclass_callbacks")
-
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")
@@ -468,47 +465,43 @@
#
# 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, *filter_list, &block)
- __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
+ def set_callback(name, *filters, &block)
+ __update_callbacks(name, filters, block) do |chain, type, filters, options|
filters.map! do |filter|
- removed = chain.delete_if {|c| c.matches?(type, filter) }
- send("_removed_#{name}_callbacks").push(*removed)
+ chain.delete_if {|c| c.matches?(type, filter) }
Callback.new(chain, filter, type, options.dup, self)
end
options[:prepend] ? chain.unshift(*filters) : chain.push(*filters)
end
end
# Skip a previously defined callback for a given type.
#
- def skip_callback(name, *filter_list, &block)
- __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
+ def skip_callback(name, *filters, &block)
+ __update_callbacks(name, filters, block) do |chain, type, filters, options|
+ chain = send("_#{name}_callbacks=", chain.clone(self))
+
filters.each do |filter|
filter = chain.find {|c| c.matches?(type, filter) }
if filter && options.any?
- new_filter = filter.clone(chain, self)
- chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options, options[:per_key] || {})
+ filter.recompile!(options, options[:per_key] || {})
+ else
+ chain.delete(filter)
end
-
- chain.delete(filter)
- send("_removed_#{name}_callbacks") << filter
end
end
end
# Reset callbacks for a given type.
#
def reset_callbacks(symbol)
- callbacks = send("_#{symbol}_callbacks")
- callbacks.clear
- send("_removed_#{symbol}_callbacks").concat(callbacks)
+ send("_#{symbol}_callbacks").clear
__define_runner(symbol)
end
# Define callbacks types.
#
@@ -529,11 +522,11 @@
# * <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.
#
# * <tt>:scope</tt> - Show which methods should be executed when a class
- # is given as callback:
+ # is giben as callback:
#
# define_callbacks :filters, :scope => [ :kind ]
#
# When a class is given:
#
@@ -551,49 +544,17 @@
#
# before_validate MyValidation
#
# Defaults to :kind.
#
- 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)
+ 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)
end
- extlib_inheritable_reader("_removed_#{callback}_callbacks") do
- []
- end
-
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def self._#{callback}_superclass_callbacks
- if superclass.respond_to?(:_#{callback}_callbacks)
- superclass._#{callback}_callbacks + superclass._#{callback}_superclass_callbacks
- else
- []
- end
- end
-
- def self._update_#{callback}_superclass_callbacks
- changed, index = false, 0
-
- callbacks = (_#{callback}_superclass_callbacks -
- _#{callback}_callbacks) - _removed_#{callback}_callbacks
-
- callbacks.each do |callback|
- if new_index = _#{callback}_callbacks.index(callback)
- index = new_index + 1
- else
- changed = true
- _#{callback}_callbacks.insert(index, callback)
- index = index + 1
- end
- end
- changed
- end
- METHOD
-
- __define_runner(callback)
+ __define_runner(symbol)
end
end
end
end
end