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