diff options
Diffstat (limited to 'activesupport/lib/active_support/deprecated_callbacks.rb')
-rw-r--r-- | activesupport/lib/active_support/deprecated_callbacks.rb | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/deprecated_callbacks.rb b/activesupport/lib/active_support/deprecated_callbacks.rb new file mode 100644 index 0000000000..20fb03cbeb --- /dev/null +++ b/activesupport/lib/active_support/deprecated_callbacks.rb @@ -0,0 +1,283 @@ +require 'active_support/core_ext/array/extract_options' + +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. + # + # Mixing in this module allows you to define callbacks in your class. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # end + # + # class ConfigStorage < Storage + # before_save :saving_message + # def saving_message + # puts "saving..." + # end + # + # after_save do |object| + # puts "saved" + # end + # + # def save + # run_callbacks(:before_save) + # puts "- save" + # run_callbacks(:after_save) + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # saving... + # - save + # saved + # + # Callbacks from parent classes are inherited. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # + # before_save :prepare + # def prepare + # puts "preparing save" + # end + # end + # + # class ConfigStorage < Storage + # before_save :saving_message + # def saving_message + # puts "saving..." + # end + # + # after_save do |object| + # puts "saved" + # end + # + # def save + # run_callbacks(:before_save) + # puts "- save" + # run_callbacks(:after_save) + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # preparing save + # saving... + # - save + # saved + module DeprecatedCallbacks + class CallbackChain < Array + def self.build(kind, *methods, &block) + methods, options = extract_options(*methods, &block) + methods.map! { |method| Callback.new(kind, method, options) } + new(methods) + end + + def run(object, options = {}, &terminator) + enumerator = options[:enumerator] || :each + + unless block_given? + send(enumerator) { |callback| callback.call(object) } + else + send(enumerator) do |callback| + result = callback.call(object) + break result if terminator.call(result, object) + end + end + end + + # TODO: Decompose into more Array like behavior + def replace_or_append!(chain) + if index = index(chain) + self[index] = chain + else + self << chain + end + self + end + + def find(callback, &block) + select { |c| c == callback && (!block_given? || yield(c)) }.first + end + + def delete(callback) + super(callback.is_a?(Callback) ? callback : find(callback)) + end + + private + def self.extract_options(*methods, &block) + methods.flatten! + options = methods.extract_options! + methods << block if block_given? + return methods, options + end + + def extract_options(*methods, &block) + self.class.extract_options(*methods, &block) + end + end + + class Callback + attr_reader :kind, :method, :identifier, :options + + def initialize(kind, method, options = {}) + @kind = kind + @method = method + @identifier = options[:identifier] + @options = options + end + + def ==(other) + case other + when Callback + (self.identifier && self.identifier == other.identifier) || self.method == other.method + else + (self.identifier && self.identifier == other) || self.method == other + end + end + + def eql?(other) + self == other + end + + def dup + self.class.new(@kind, @method, @options.dup) + end + + def hash + if @identifier + @identifier.hash + else + @method.hash + end + end + + def call(*args, &block) + evaluate_method(method, *args, &block) if should_run_callback?(*args) + rescue LocalJumpError + raise ArgumentError, + "Cannot yield from a Proc type filter. The Proc must take two " + + "arguments and execute #call on the second argument." + end + + private + def evaluate_method(method, *args, &block) + case method + when Symbol + object = args.shift + object.send(method, *args, &block) + when String + eval(method, args.first.instance_eval { binding }) + when Proc, Method + method.call(*args, &block) + else + if method.respond_to?(kind) + method.send(kind, *args, &block) + else + raise ArgumentError, + "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + + "a block to be invoked, or an object responding to the callback method." + end + end + end + + def should_run_callback?(*args) + [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } && + ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) } + end + end + + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def define_callbacks(*callbacks) + ActiveSupport::Deprecation.warn('ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks', caller) + + callbacks.each do |callback| + class_eval <<-"end_eval", __FILE__, __LINE__ + 1 + def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block) + callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block) + @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new + @#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks + end # end + # + def self.#{callback}_callback_chain # def self.before_save_callback_chain + @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new + # + if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain) + CallbackChain.new( # CallbackChain.new( + superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain + + @#{callback}_callbacks # @before_save_callbacks + ) # ) + else # else + @#{callback}_callbacks # @before_save_callbacks + end # end + end # end + end_eval + end + end + end + + # Runs all the callbacks defined for the given options. + # + # If a block is given it will be called after each callback receiving as arguments: + # + # * the result from the callback + # * the object which has the callback + # + # If the result from the block evaluates to +true+, the callback chain is stopped. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # end + # + # class ConfigStorage < Storage + # before_save :pass + # before_save :pass + # before_save :stop + # before_save :pass + # + # def pass + # puts "pass" + # end + # + # def stop + # puts "stop" + # return false + # end + # + # def save + # result = run_callbacks(:before_save) { |result, object| result == false } + # puts "- save" if result + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # pass + # pass + # stop + def run_callbacks(kind, options = {}, &block) + self.class.send("#{kind}_callback_chain").run(self, options, &block) + end + end +end |