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