callbacks.rb 11.0 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActiveRecord
2
  # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
3
  # before or after an alteration of the object state. This can be used to make sure that associated and
P
Pratik Naik 已提交
4
  # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
5
  # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
P
Pratik Naik 已提交
6
  # the <tt>Base#save</tt> call for a new record:
7 8 9 10 11 12
  #
  # * (-) <tt>save</tt>
  # * (-) <tt>valid</tt>
  # * (1) <tt>before_validation</tt>
  # * (-) <tt>validate</tt>
  # * (-) <tt>validate_on_create</tt>
13 14 15
  # * (2) <tt>after_validation</tt>
  # * (3) <tt>before_save</tt>
  # * (4) <tt>before_create</tt>
16
  # * (-) <tt>create</tt>
17 18
  # * (5) <tt>after_create</tt>
  # * (6) <tt>after_save</tt>
19
  #
20
  # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
21
  # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each
P
Pratik Naik 已提交
22
  # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
D
Initial  
David Heinemeier Hansson 已提交
23 24 25
  #
  # Examples:
  #   class CreditCard < ActiveRecord::Base
26
  #     # Strip everything but digits, so the user can specify "555 234 34" or
D
Initial  
David Heinemeier Hansson 已提交
27 28 29 30 31 32 33
  #     # "5552-3434" or both will mean "55523434"
  #     def before_validation_on_create
  #       self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
  #     end
  #   end
  #
  #   class Subscription < ActiveRecord::Base
34 35 36 37 38 39
  #     before_create :record_signup
  #
  #     private
  #       def record_signup
  #         self.signed_up_on = Date.today
  #       end
D
Initial  
David Heinemeier Hansson 已提交
40 41 42 43
  #   end
  #
  #   class Firm < ActiveRecord::Base
  #     # Destroys the associated clients and people when the firm is destroyed
44 45 46
  #     before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  #     before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
  #   end
D
Initial  
David Heinemeier Hansson 已提交
47 48 49
  #
  # == Inheritable callback queues
  #
P
Pratik Naik 已提交
50
  # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
D
Initial  
David Heinemeier Hansson 已提交
51 52 53 54 55 56 57 58 59 60 61
  # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
  # hierarchy. Example:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy :destroy_author
  #   end
  #
  #   class Reply < Topic
  #     before_destroy :destroy_readers
  #   end
  #
62 63
  # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is run, both +destroy_author+ and
  # +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable
D
Initial  
David Heinemeier Hansson 已提交
64 65 66 67 68 69 70 71 72 73
  # methods:
  #
  #   class Topic < ActiveRecord::Base
  #     def before_destroy() destroy_author end
  #   end
  #
  #   class Reply < Topic
  #     def before_destroy() destroy_readers end
  #   end
  #
74 75
  # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
  # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
P
Pratik Naik 已提交
76
  # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
D
Initial  
David Heinemeier Hansson 已提交
77
  #
78 79 80 81
  # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
  # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
  # be inherited.
  #
D
Initial  
David Heinemeier Hansson 已提交
82 83
  # == Types of callbacks
  #
84
  # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
D
Initial  
David Heinemeier Hansson 已提交
85
  # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
D
David Heinemeier Hansson 已提交
86
  # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
D
Initial  
David Heinemeier Hansson 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  # eval methods are deprecated.
  #
  # The method reference callbacks work by specifying a protected or private method available in the object, like this:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy :delete_parents
  #
  #     private
  #       def delete_parents
  #         self.class.delete_all "parent_id = #{id}"
  #       end
  #   end
  #
  # The callback objects have methods named after the callback called with the record as the only parameter, such as:
  #
  #   class BankAccount < ActiveRecord::Base
P
Pratik Naik 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
  #     before_save      EncryptionWrapper.new
  #     after_save       EncryptionWrapper.new
  #     after_initialize EncryptionWrapper.new
  #   end
  #
  #   class EncryptionWrapper
  #     def before_save(record)
  #       record.credit_card_number = encrypt(record.credit_card_number)
  #     end
  #
  #     def after_save(record)
  #       record.credit_card_number = decrypt(record.credit_card_number)
  #     end
  #
  #     alias_method :after_find, :after_save
  #
  #     private
  #       def encrypt(value)
  #         # Secrecy is committed
  #       end
  #
  #       def decrypt(value)
  #         # Secrecy is unveiled
  #       end
  #   end
  #
  # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
  # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
  # initialization data such as the name of the attribute to work with:
  #
  #   class BankAccount < ActiveRecord::Base
D
Initial  
David Heinemeier Hansson 已提交
134 135 136 137 138 139 140 141 142 143 144
  #     before_save      EncryptionWrapper.new("credit_card_number")
  #     after_save       EncryptionWrapper.new("credit_card_number")
  #     after_initialize EncryptionWrapper.new("credit_card_number")
  #   end
  #
  #   class EncryptionWrapper
  #     def initialize(attribute)
  #       @attribute = attribute
  #     end
  #
  #     def before_save(record)
P
Pratik Naik 已提交
145
  #       record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
D
Initial  
David Heinemeier Hansson 已提交
146 147 148
  #     end
  #
  #     def after_save(record)
P
Pratik Naik 已提交
149
  #       record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
D
Initial  
David Heinemeier Hansson 已提交
150
  #     end
151
  #
D
David Heinemeier Hansson 已提交
152
  #     alias_method :after_find, :after_save
D
Initial  
David Heinemeier Hansson 已提交
153 154 155 156 157 158 159
  #
  #     private
  #       def encrypt(value)
  #         # Secrecy is committed
  #       end
  #
  #       def decrypt(value)
160
  #         # Secrecy is unveiled
D
Initial  
David Heinemeier Hansson 已提交
161 162 163 164 165 166 167 168 169 170
  #       end
  #   end
  #
  # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
  # which will then be evaluated within the binding of the callback. Example:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy 'self.class.delete_all "parent_id = #{id}"'
  #   end
  #
171
  # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback is triggered. Also note that these
D
Initial  
David Heinemeier Hansson 已提交
172 173 174
  # inline callbacks can be stacked just like the regular ones:
  #
  #   class Topic < ActiveRecord::Base
175
  #     before_destroy 'self.class.delete_all "parent_id = #{id}"',
D
Initial  
David Heinemeier Hansson 已提交
176 177 178
  #                    'puts "Evaluated after parents are destroyed"'
  #   end
  #
179
  # == The +after_find+ and +after_initialize+ exceptions
D
Initial  
David Heinemeier Hansson 已提交
180
  #
181 182 183
  # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder, such as <tt>Base.find(:all)</tt>, we've had
  # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and
  # +after_initialize+ will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
184
  # callback types will be called.
185
  #
186
  # == <tt>before_validation*</tt> returning statements
187
  #
188
  # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
P
Pratik Naik 已提交
189
  # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
190 191
  # Nothing will be appended to the errors object.
  #
192
  # == Canceling callbacks
193
  #
194 195
  # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns
  # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
196
  # defined as methods on the model, which are called last.
197 198 199 200 201 202 203 204 205 206 207 208
  #
  # == Transactions
  #
  # The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
  # within a transaction. That includes <tt>after_*</tt> hooks. If everything
  # goes fine a COMMIT is executed once the chain has been completed.
  #
  # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
  # can also trigger a ROLLBACK raising an exception in any of the callbacks,
  # including <tt>after_*</tt> hooks. Note, however, that in that case the client
  # needs to be aware of it because an ordinary +save+ will raise such exception
  # instead of quietly returning +false+.
D
Initial  
David Heinemeier Hansson 已提交
209
  module Callbacks
210
    extend ActiveSupport::Concern
211

212 213
    CALLBACKS = [
      :after_initialize, :after_find, :before_validation, :after_validation,
214 215 216
      :before_save, :around_save, :after_save, :before_create, :around_create,
      :after_create, :before_update, :around_update, :after_update,
      :before_destroy, :around_destroy, :after_destroy
217
    ]
D
Initial  
David Heinemeier Hansson 已提交
218

219
    included do
220
      [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
221
        alias_method_chain method, :callbacks
D
Initial  
David Heinemeier Hansson 已提交
222 223
      end

224 225 226 227 228
      extend ActiveModel::Callbacks

      define_model_callbacks :initialize, :find, :only => :after
      define_model_callbacks :save, :create, :update, :destroy
      define_model_callbacks :validation, :only => [:before, :after]
D
Initial  
David Heinemeier Hansson 已提交
229 230
    end

231 232 233 234 235 236 237 238
    module ClassMethods
      def method_added(meth)
        super
        if CALLBACKS.include?(meth.to_sym)
          ActiveSupport::Deprecation.warn("Base##{meth} has been deprecated, please use Base.#{meth} :method instead", caller[0,1])
          send(meth.to_sym, meth.to_sym)
        end
      end
D
Initial  
David Heinemeier Hansson 已提交
239 240
    end

241 242 243 244 245 246
    def create_or_update_with_callbacks #:nodoc:
      _run_save_callbacks do
        create_or_update_without_callbacks
      end
    end
    private :create_or_update_with_callbacks
D
Initial  
David Heinemeier Hansson 已提交
247

248 249 250 251
    def create_with_callbacks #:nodoc:
      _run_create_callbacks do
        create_without_callbacks
      end
D
Initial  
David Heinemeier Hansson 已提交
252
    end
253
    private :create_with_callbacks
D
Initial  
David Heinemeier Hansson 已提交
254

255 256 257 258 259 260
    def update_with_callbacks(*args) #:nodoc:
      _run_update_callbacks do
        update_without_callbacks(*args)
      end
    end
    private :update_with_callbacks
261

262 263 264 265 266 267
    def valid_with_callbacks? #:nodoc:
      @_on_validate = new_record? ? :create : :update
      _run_validation_callbacks do
        valid_without_callbacks?
      end
    end
268

269 270 271 272 273
    def destroy_with_callbacks #:nodoc:
      _run_destroy_callbacks do
        destroy_without_callbacks
      end
    end
274

275 276
    def deprecated_callback_method(symbol) #:nodoc:
      if respond_to?(symbol)
277
        ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
278
        send(symbol)
D
Initial  
David Heinemeier Hansson 已提交
279
      end
280
    end
D
Initial  
David Heinemeier Hansson 已提交
281
  end
282
end