File: active_support/callbacks.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: ActiveSupport#1
  module: Callbacks#78
has properties
module method: included / 1 #200
method: run_callbacks / 3 #275
  class: CallbackChain#79
inherits from
  Array ( Builtin-Module )
has properties
class method: build / 3 #80
method: run / 3 #86
method: replace_or_append! / 1 #100
method: find / 2 #109
method: delete / 1 #113
class method: extract_options (1/2) / 2 #118
method: extract_options (2/E) / 2 #125
  class: Callback#130
inherits from
  Object ( Builtin-Module )
has properties
attribute: kind [R] #131
attribute: method [R] #131
attribute: identifier [R] #131
attribute: options [R] #131
method: initialize / 3 #133
method: == / 1 #140
method: eql? / 1 #149
method: dup #153
method: hash #157
method: call / 2 #165
method: evaluate_method / 3 #174
method: should_run_callback? / 1 #194
  module: ClassMethods#204
has properties
method: define_callbacks / 1 #205

Code

   1  module ActiveSupport
   2    # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
   3    # before or after an alteration of the object state.
   4    #
   5    # Mixing in this module allows you to define callbacks in your class.
   6    #
   7    # Example:
   8    #   class Storage
   9    #     include ActiveSupport::Callbacks
  10    #
  11    #     define_callbacks :before_save, :after_save
  12    #   end
  13    #
  14    #   class ConfigStorage < Storage
  15    #     before_save :saving_message
  16    #     def saving_message
  17    #       puts "saving..."
  18    #     end
  19    #
  20    #     after_save do |object|
  21    #       puts "saved"
  22    #     end
  23    #
  24    #     def save
  25    #       run_callbacks(:before_save)
  26    #       puts "- save"
  27    #       run_callbacks(:after_save)
  28    #     end
  29    #   end
  30    #
  31    #   config = ConfigStorage.new
  32    #   config.save
  33    #
  34    # Output:
  35    #   saving...
  36    #   - save
  37    #   saved
  38    #
  39    # Callbacks from parent classes are inherited.
  40    #
  41    # Example:
  42    #   class Storage
  43    #     include ActiveSupport::Callbacks
  44    #
  45    #     define_callbacks :before_save, :after_save
  46    #
  47    #     before_save :prepare
  48    #     def prepare
  49    #       puts "preparing save"
  50    #     end
  51    #   end
  52    #
  53    #   class ConfigStorage < Storage
  54    #     before_save :saving_message
  55    #     def saving_message
  56    #       puts "saving..."
  57    #     end
  58    #
  59    #     after_save do |object|
  60    #       puts "saved"
  61    #     end
  62    #
  63    #     def save
  64    #       run_callbacks(:before_save)
  65    #       puts "- save"
  66    #       run_callbacks(:after_save)
  67    #     end
  68    #   end
  69    #
  70    #   config = ConfigStorage.new
  71    #   config.save
  72    #
  73    # Output:
  74    #   preparing save
  75    #   saving...
  76    #   - save
  77    #   saved
  78    module Callbacks
  79      class CallbackChain < Array
  80        def self.build(kind, *methods, &block)
  81          methods, options = extract_options(*methods, &block)
  82          methods.map! { |method| Callback.new(kind, method, options) }
  83          new(methods)
  84        end
  85 
  86        def run(object, options = {}, &terminator)
  87          enumerator = options[:enumerator] || :each
  88 
  89          unless block_given?
  90            send(enumerator) { |callback| callback.call(object) }
  91          else
  92            send(enumerator) do |callback|
  93              result = callback.call(object)
  94              break result if terminator.call(result, object)
  95            end
  96          end
  97        end
  98 
  99        # TODO: Decompose into more Array like behavior
 100        def replace_or_append!(chain)
 101          if index = index(chain)
 102            self[index] = chain
 103          else
 104            self << chain
 105          end
 106          self
 107        end
 108 
 109        def find(callback, &block)
 110          select { |c| c == callback && (!block_given? || yield(c)) }.first
 111        end
 112 
 113        def delete(callback)
 114          super(callback.is_a?(Callback) ? callback : find(callback))
 115        end
 116 
 117        private
 118          def self.extract_options(*methods, &block)
 119            methods.flatten!
 120            options = methods.extract_options!
 121            methods << block if block_given?
 122            return methods, options
 123          end
 124 
 125          def extract_options(*methods, &block)
 126            self.class.extract_options(*methods, &block)
 127          end
 128      end
 129 
 130      class Callback
 131        attr_reader :kind, :method, :identifier, :options
 132 
 133        def initialize(kind, method, options = {})
 134          @kind       = kind
 135          @method     = method
 136          @identifier = options[:identifier]
 137          @options    = options
 138        end
 139 
 140        def ==(other)
 141          case other
 142          when Callback
 143            (self.identifier && self.identifier == other.identifier) || self.method == other.method
 144          else
 145            (self.identifier && self.identifier == other) || self.method == other
 146          end
 147        end
 148 
 149        def eql?(other)
 150          self == other
 151        end
 152 
 153        def dup
 154          self.class.new(@kind, @method, @options.dup)
 155        end
 156 
 157        def hash
 158          if @identifier
 159            @identifier.hash
 160          else
 161            @method.hash
 162          end
 163        end
 164 
 165        def call(*args, &block)
 166          evaluate_method(method, *args, &block) if should_run_callback?(*args)
 167        rescue LocalJumpError
 168          raise ArgumentError,
 169            "Cannot yield from a Proc type filter. The Proc must take two " +
 170            "arguments and execute #call on the second argument."
 171        end
 172 
 173        private
 174          def evaluate_method(method, *args, &block)
 175            case method
 176              when Symbol
 177                object = args.shift
 178                object.send(method, *args, &block)
 179              when String
 180                eval(method, args.first.instance_eval { binding })
 181              when Proc, Method
 182                method.call(*args, &block)
 183              else
 184                if method.respond_to?(kind)
 185                  method.send(kind, *args, &block)
 186                else
 187                  raise ArgumentError,
 188                    "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
 189                    "a block to be invoked, or an object responding to the callback method."
 190                end
 191            end
 192          end
 193 
 194          def should_run_callback?(*args)
 195            [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } &&
 196            ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) }
 197          end
 198      end
 199 
 200      def self.included(base)
 201        base.extend ClassMethods
 202      end
 203 
 204      module ClassMethods
 205        def define_callbacks(*callbacks)
 206          callbacks.each do |callback|
 207            class_eval <<-"end_eval"
 208              def self.#{callback}(*methods, &block)                             # def self.before_save(*methods, &block)
 209                callbacks = CallbackChain.build(:#{callback}, *methods, &block)  #   callbacks = CallbackChain.build(:before_save, *methods, &block)
 210                @#{callback}_callbacks ||= CallbackChain.new                     #   @before_save_callbacks ||= CallbackChain.new
 211                @#{callback}_callbacks.concat callbacks                          #   @before_save_callbacks.concat callbacks
 212              end                                                                # end
 213                                                                                 #
 214              def self.#{callback}_callback_chain                                # def self.before_save_callback_chain
 215                @#{callback}_callbacks ||= CallbackChain.new                     #   @before_save_callbacks ||= CallbackChain.new
 216                                                                                 #
 217                if superclass.respond_to?(:#{callback}_callback_chain)           #   if superclass.respond_to?(:before_save_callback_chain)
 218                  CallbackChain.new(                                             #     CallbackChain.new(
 219                    superclass.#{callback}_callback_chain +                      #       superclass.before_save_callback_chain +
 220                    @#{callback}_callbacks                                       #       @before_save_callbacks
 221                  )                                                              #     )
 222                else                                                             #   else
 223                  @#{callback}_callbacks                                         #     @before_save_callbacks
 224                end                                                              #   end
 225              end                                                                # end
 226            end_eval
 227          end
 228        end
 229      end
 230 
 231      # Runs all the callbacks defined for the given options.
 232      #
 233      # If a block is given it will be called after each callback receiving as arguments:
 234      #
 235      #  * the result from the callback
 236      #  * the object which has the callback
 237      #
 238      # If the result from the block evaluates to false, the callback chain is stopped.
 239      #
 240      # Example:
 241      #   class Storage
 242      #     include ActiveSupport::Callbacks
 243      #
 244      #     define_callbacks :before_save, :after_save
 245      #   end
 246      #
 247      #   class ConfigStorage < Storage
 248      #     before_save :pass
 249      #     before_save :pass
 250      #     before_save :stop
 251      #     before_save :pass
 252      #
 253      #     def pass
 254      #       puts "pass"
 255      #     end
 256      #
 257      #     def stop
 258      #       puts "stop"
 259      #       return false
 260      #     end
 261      #
 262      #     def save
 263      #       result = run_callbacks(:before_save) { |result, object| result == false }
 264      #       puts "- save" if result
 265      #     end
 266      #   end
 267      #
 268      #   config = ConfigStorage.new
 269      #   config.save
 270      #
 271      # Output:
 272      #   pass
 273      #   pass
 274      #   stop
 275      def run_callbacks(kind, options = {}, &block)
 276        self.class.send("#{kind}_callback_chain").run(self, options, &block)
 277      end
 278    end
 279  end