Froze rails gems
[depot.git] / vendor / rails / activesupport / lib / active_support / callbacks.rb
diff --git a/vendor/rails/activesupport/lib/active_support/callbacks.rb b/vendor/rails/activesupport/lib/active_support/callbacks.rb
new file mode 100644 (file)
index 0000000..5cdcaf5
--- /dev/null
@@ -0,0 +1,280 @@
+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::Callbacks
+  #
+  #     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::Callbacks
+  #
+  #     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 Callbacks
+    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)
+          if options[:if]
+            evaluate_method(options[:if], *args)
+          elsif options[:unless]
+            !evaluate_method(options[:unless], *args)
+          else
+            true
+          end
+        end
+    end
+
+    def self.included(base)
+      base.extend ClassMethods
+    end
+
+    module ClassMethods
+      def define_callbacks(*callbacks)
+        callbacks.each do |callback|
+          class_eval <<-"end_eval"
+            def self.#{callback}(*methods, &block)
+              callbacks = CallbackChain.build(:#{callback}, *methods, &block)
+              (@#{callback}_callbacks ||= CallbackChain.new).concat callbacks
+            end
+
+            def self.#{callback}_callback_chain
+              @#{callback}_callbacks ||= CallbackChain.new
+
+              if superclass.respond_to?(:#{callback}_callback_chain)
+                CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
+              else
+                @#{callback}_callbacks
+              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 false, the callback chain is stopped.
+    #
+    # Example:
+    #   class Storage
+    #     include ActiveSupport::Callbacks
+    #
+    #     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