lib/active_support/callbacks.rb in activesupport-3.1.12 vs lib/active_support/callbacks.rb in activesupport-3.2.0.rc1
- old
+ new
@@ -79,10 +79,18 @@
#
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
+ private
+
+ # A hook invoked everytime a before callback is halted.
+ # This can be overriden in AS::Callback implementors in order
+ # to provide better debugging/logging.
+ def halted_callback_hook(filter)
+ end
+
class Callback #:nodoc:#
@@_callback_sequence = 0
attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
@@ -151,11 +159,11 @@
def _compile_per_key_options
key_options = _compile_options(@per_key)
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _one_time_conditions_valid_#{@callback_id}?
- true #{key_options[0]}
+ true if #{key_options}
end
RUBY_EVAL
end
# This will supply contents for before and around filters, and no
@@ -169,22 +177,22 @@
case @kind
when :before
# if condition # before_save :filter_name, :if => :condition
# filter_name
# end
- filter = <<-RUBY_EVAL
- unless halted
- # This double assignment is to prevent warnings in 1.9.3. I would
- # remove the `result` variable, but apparently some other
- # generated code is depending on this variable being set sometimes
- # and sometimes not.
+ <<-RUBY_EVAL
+ if !halted && #{@compiled_options}
+ # This double assignment is to prevent warnings in 1.9.3 as
+ # the `result` variable is not always used except if the
+ # terminator code refers to it.
result = result = #{@filter}
halted = (#{chain.config[:terminator]})
+ if halted
+ halted_callback_hook(#{@raw_filter.inspect.inspect})
+ end
end
RUBY_EVAL
-
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
when :around
# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
# For `around_save :filter_name, :if => :condition':
@@ -200,11 +208,11 @@
# end
#
name = "_conditional_callback_#{@kind}_#{next_id}"
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}(halted)
- #{@compiled_options[0] || "if true"} && !halted
+ if #{@compiled_options} && !halted
#{@filter} do
yield self
end
else
yield self
@@ -220,14 +228,16 @@
def end(key=nil, object=nil)
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
case @kind
when :after
- # if condition # after_save :filter_name, :if => :condition
- # filter_name
- # end
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
+ # after_save :filter_name, :if => :condition
+ <<-RUBY_EVAL
+ if #{@compiled_options}
+ #{@filter}
+ end
+ RUBY_EVAL
when :around
<<-RUBY_EVAL
value
end
RUBY_EVAL
@@ -238,23 +248,21 @@
# Options support the same options as filters themselves (and support
# symbols, string, procs, and objects), so compile a conditional
# expression based on the options
def _compile_options(options)
- return [] if options[:if].empty? && options[:unless].empty?
+ conditions = ["true"]
- conditions = []
-
unless options[:if].empty?
conditions << Array.wrap(_compile_filter(options[:if]))
end
unless options[:unless].empty?
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
end
- ["if #{conditions.flatten.join(" && ")}", "end"]
+ conditions.flatten.join(" && ")
end
# Filters support:
#
# Arrays:: Used in conditions. This is used to specify
@@ -369,45 +377,40 @@
module ClassMethods
# Generate the internal runner method called by +run_callbacks+.
def __define_runner(symbol) #:nodoc:
body = send("_#{symbol}_callbacks").compile
+ runner_method = "_run_#{symbol}_callbacks"
silence_warnings do
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
+ undef_method runner_method if method_defined?(runner_method)
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _run_#{symbol}_callbacks(key = nil, &blk)
+ def #{runner_method}(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
-
- send(name, &blk)
+ self.class.__run_keyed_callback(key, :#{symbol}, self, &blk)
else
#{body}
end
end
- private :_run_#{symbol}_callbacks
+ private :#{runner_method}
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
- # which callbacks can be omitted because of per_key conditions.
+ # This method calls the callback method for the given key.
+ # If this called first time it creates a new callback method for the key,
+ # calculating which callbacks can be omitted because of per_key conditions.
#
- def __create_keyed_callback(name, kind, object, &blk) #:nodoc:
- @_keyed_callbacks ||= {}
- @_keyed_callbacks[name] ||= begin
+ def __run_keyed_callback(key, kind, object, &blk) #:nodoc:
+ name = "_run__#{self.name.hash.abs}__#{kind}__#{key.hash.abs}__callbacks"
+ unless object.respond_to?(name)
str = send("_#{kind}_callbacks").compile(name, object)
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}() #{str} end
protected :#{name}
RUBY_EVAL
- true
end
+ object.send(name, &blk)
end
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
#