aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/new_callbacks.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/new_callbacks.rb')
-rw-r--r--activesupport/lib/active_support/new_callbacks.rb201
1 files changed, 105 insertions, 96 deletions
diff --git a/activesupport/lib/active_support/new_callbacks.rb b/activesupport/lib/active_support/new_callbacks.rb
index 56b510d52e..ce08ea8660 100644
--- a/activesupport/lib/active_support/new_callbacks.rb
+++ b/activesupport/lib/active_support/new_callbacks.rb
@@ -15,17 +15,17 @@ module ActiveSupport
# end
#
# class ConfigStorage < Storage
- # save_callback :before, :saving_message
+ # set_callback :save, :before, :saving_message
# def saving_message
# puts "saving..."
# end
#
- # save_callback :after do |object|
+ # set_callback :save, :after do |object|
# puts "saved"
# end
#
# def save
- # _run_save_callbacks do
+ # _run_set_callback :save,s do
# puts "- save"
# end
# end
@@ -47,24 +47,24 @@ module ActiveSupport
#
# define_callbacks :save
#
- # save_callback :before, :prepare
+ # set_callback :save, :before, :prepare
# def prepare
# puts "preparing save"
# end
# end
#
# class ConfigStorage < Storage
- # save_callback :before, :saving_message
+ # set_callback :save, :before, :saving_message
# def saving_message
# puts "saving..."
# end
#
- # save_callback :after do |object|
+ # set_callback :save, :after do |object|
# puts "saved"
# end
#
# def save
- # _run_save_callbacks do
+ # _run_set_callback :save,s do
# puts "- save"
# end
# end
@@ -78,22 +78,23 @@ module ActiveSupport
# saving...
# - save
# saved
+ #
module NewCallbacks
def self.included(klass)
klass.extend ClassMethods
end
-
+
def run_callbacks(kind, options = {}, &blk)
send("_run_#{kind}_callbacks", &blk)
end
-
+
class Callback
@@_callback_sequence = 0
-
- attr_accessor :filter, :kind, :name, :options, :per_key, :klass
- def initialize(filter, kind, options, klass)
- @kind, @klass = kind, klass
-
+
+ attr_accessor :name, :filter, :kind, :options, :per_key, :klass
+
+ def initialize(name, filter, kind, options, klass)
+ @name, @kind, @klass = name, kind, klass
normalize_options!(options)
@per_key = options.delete(:per_key)
@@ -104,9 +105,10 @@ module ActiveSupport
_compile_per_key_options
end
-
+
def clone(klass)
obj = super()
+ obj.name = name
obj.klass = klass
obj.per_key = @per_key.dup
obj.options = @options.dup
@@ -114,36 +116,39 @@ module ActiveSupport
obj.per_key[:unless] = @per_key[:unless].dup
obj.options[:if] = @options[:if].dup
obj.options[:unless] = @options[:unless].dup
+ obj.options[:scope] = @options[:scope].dup
obj
end
-
+
def normalize_options!(options)
options[:if] = Array.wrap(options[:if])
options[:unless] = Array.wrap(options[:unless])
+ options[:scope] ||= [:kind]
+ options[:scope] = Array.wrap(options[:scope])
+
options[:per_key] ||= {}
options[:per_key][:if] = Array.wrap(options[:per_key][:if])
options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
end
-
+
def next_id
@@_callback_sequence += 1
end
-
+
def matches?(_kind, _filter)
- @kind == _kind &&
- @filter == _filter
+ @kind == _kind && @filter == _filter
end
def _update_filter(filter_options, new_options)
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
end
-
+
def recompile!(_options, _per_key)
_update_filter(self.options, _options)
_update_filter(self.per_key, _per_key)
-
+
@callback_id = next_id
@filter = _compile_filter(@raw_filter)
@compiled_options = _compile_options(@options)
@@ -164,14 +169,13 @@ module ActiveSupport
# contents for after filters (for the forward pass).
def start(key = nil, options = {})
object, terminator = (options || {}).values_at(:object, :terminator)
-
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
+
terminator ||= false
-
+
# options[0] is the compiled form of supplied conditions
# options[1] is the "end" for the conditional
-
+
if @kind == :before || @kind == :around
if @kind == :before
# if condition # before_save :filter_name, :if => :condition
@@ -183,7 +187,7 @@ module ActiveSupport
halted = (#{terminator})
end
RUBY_EVAL
-
+
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
else
# Compile around filters with conditions into proxy methods
@@ -200,7 +204,7 @@ module ActiveSupport
# yield self
# end
# end
-
+ #
name = "_conditional_callback_#{@kind}_#{next_id}"
txt, line = <<-RUBY_EVAL, __LINE__ + 1
def #{name}(halted)
@@ -223,9 +227,8 @@ module ActiveSupport
# before filters (for the backward pass).
def end(key = nil, options = {})
object = (options || {})[:object]
-
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
+
if @kind == :around || @kind == :after
# if condition # after_save :filter_name, :if => :condition
# filter_name
@@ -237,28 +240,30 @@ module ActiveSupport
end
end
end
-
+
private
+
# 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)
+ def _compile_options(options)
return [] if options[:if].empty? && options[:unless].empty?
-
+
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"]
end
-
+
# Filters support:
+ #
# Arrays:: Used in conditions. This is used to specify
# multiple conditions. Used internally to
# merge conditions from skip_* filters
@@ -269,6 +274,7 @@ module ActiveSupport
#
# All of these objects are compiled into methods and handled
# the same after this point:
+ #
# Arrays:: Merged together into a single filter
# Symbols:: Already methods
# Strings:: class_eval'ed into methods
@@ -276,6 +282,7 @@ module ActiveSupport
# Objects::
# a method is created that calls the before_foo method
# on the object.
+ #
def _compile_filter(filter)
method_name = "_callback_#{@kind}_#{next_id}"
case filter
@@ -294,10 +301,11 @@ module ActiveSupport
@klass.send(:define_method, "#{method_name}_object") { filter }
_normalize_legacy_filter(kind, filter)
+ method_to_call = @options[:scope].map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{method_name}(&blk)
- #{method_name}_object.send(:#{kind}, self, &blk)
+ #{method_name}_object.send(:#{method_to_call}, self, &blk)
end
RUBY_EVAL
@@ -318,59 +326,67 @@ module ActiveSupport
end
end
end
-
end
# An Array with a compile method
class CallbackChain < Array
- def initialize(symbol)
+ attr_reader :symbol, :config
+
+ def initialize(symbol, config)
@symbol = symbol
+ @config = config
end
-
- def compile(key = nil, options = {})
+
+ def compile(key=nil, options={})
+ options = config.merge(options)
+
method = []
+ method << "value = nil"
method << "halted = false"
+
each do |callback|
method << callback.start(key, options)
end
- method << "yield self if block_given? && !halted"
+
+ method << "value = yield if block_given? && !halted"
+
reverse_each do |callback|
method << callback.end(key, options)
end
+
+ method << "halted ? false : (block_given? ? value : true)"
method.compact.join("\n")
end
-
+
def clone(klass)
- chain = CallbackChain.new(@symbol)
+ chain = CallbackChain.new(@symbol, @config.dup)
chain.push(*map {|c| c.clone(klass)})
end
end
-
+
module ClassMethods
- CHAINS = {:before => :before, :around => :before, :after => :after}
-
- # Make the _run_save_callbacks method. The generated method takes
+ # Make the _run_set_callback :save method. The generated method takes
# a block that it'll yield to. It'll call the before and around filters
# in order, yield the block, and then run the after filters.
#
- # _run_save_callbacks do
+ # _run_set_callback :save do
# save
# end
#
- # The _run_save_callbacks method can optionally take a key, which
+ # The _run_set_callback :save,s 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)
- body = send("_#{symbol}_callbacks").
- compile(nil, :terminator => send("_#{symbol}_terminator"))
+ #
+ 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"
-
+
unless respond_to?(name)
- self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
+ self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
end
send(name, &blk)
@@ -379,7 +395,7 @@ module ActiveSupport
end
end
RUBY_EVAL
-
+
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
class_eval body, __FILE__, line
end
@@ -387,32 +403,36 @@ module ActiveSupport
# 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.
- def _create_keyed_callback(name, kind, obj, &blk)
+ #
+ def __create_keyed_callback(name, kind, obj, &blk) #:nodoc:
@_keyed_callbacks ||= {}
@_keyed_callbacks[name] ||= begin
- str = send("_#{kind}_callbacks").
- compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
-
+ str = send("_#{kind}_callbacks").compile(name, :object => obj)
class_eval "def #{name}() #{str} end", __FILE__, __LINE__
-
true
end
end
-
+
+ def __update_callbacks(name, filters = CallbackChain.new(name, {}), block = nil)
+ type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
+ options = filters.last.is_a?(Hash) ? filters.pop : {}
+ filters.unshift(block) if block
+
+ callbacks = send("_#{name}_callbacks")
+ yield callbacks, type, filters, options if block_given?
+
+ __define_runner(name)
+ end
+
# Define callbacks.
#
- # Creates a <name>_callback method that you can use to add callbacks.
- #
# Syntax:
- # save_callback :before, :before_meth
- # save_callback :after, :after_meth, :if => :condition
- # save_callback :around {|r| stuff; yield; stuff }
- #
- # The <name>_callback method also updates the _run_<name>_callbacks
- # method, which is the public API to run the callbacks.
+ # set_callback :save, :before, :before_meth
+ # set_callback :save, :after, :after_meth, :if => :condition
+ # set_callback :save, :around {|r| stuff; yield; stuff }
#
- # Also creates a skip_<name>_callback method that you can use to skip
- # callbacks.
+ # It also updates the _run_<name>_callbacks method, which is the public
+ # API to run the callbacks. 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,
@@ -430,25 +450,12 @@ module ActiveSupport
# 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 _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
- options = filters.last.is_a?(Hash) ? filters.pop : {}
- filters.unshift(block) if block
-
- callbacks = send("_#{name}_callbacks")
- yield callbacks, type, filters, options if block_given?
-
- _define_runner(name)
- end
-
- alias_method :_reset_callbacks, :_update_callbacks
-
+ #
def set_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
+ __update_callbacks(name, filters, block) do |callbacks, type, filters, options|
filters.map! do |filter|
- # overrides parent class
callbacks.delete_if {|c| c.matches?(type, filter) }
- Callback.new(filter, type, options.dup, self)
+ Callback.new(name, filter, type, options.merge(callbacks.config), self)
end
options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
@@ -456,10 +463,9 @@ module ActiveSupport
end
def skip_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
+ __update_callbacks(name, filters, block) do |callbacks, type, filters, options|
filters.each do |filter|
callbacks = send("_#{name}_callbacks=", callbacks.clone(self))
-
filter = callbacks.find {|c| c.matches?(type, filter) }
if filter && options.any?
@@ -471,16 +477,19 @@ module ActiveSupport
end
end
+ def reset_callbacks(symbol)
+ send("_#{symbol}_callbacks").clear
+ __define_runner(symbol)
+ end
+
def define_callbacks(*symbols)
- terminator = symbols.pop if symbols.last.is_a?(String)
+ config = symbols.last.is_a?(Hash) ? symbols.pop : {}
symbols.each do |symbol|
- extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
-
extlib_inheritable_accessor("_#{symbol}_callbacks") do
- CallbackChain.new(symbol)
+ CallbackChain.new(symbol, config)
end
- _define_runner(symbol)
+ __define_runner(symbol)
end
end
end