callbacks.rb 13.7 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1 2 3
require 'observer'

module ActiveRecord
4
  # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
5
  # before or after an alteration of the object state. This can be used to make sure that associated and
P
Pratik Naik 已提交
6
  # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
7
  # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
P
Pratik Naik 已提交
8
  # the <tt>Base#save</tt> call for a new record:
9 10 11 12 13 14
  #
  # * (-) <tt>save</tt>
  # * (-) <tt>valid</tt>
  # * (1) <tt>before_validation</tt>
  # * (-) <tt>validate</tt>
  # * (-) <tt>validate_on_create</tt>
15 16 17
  # * (2) <tt>after_validation</tt>
  # * (3) <tt>before_save</tt>
  # * (4) <tt>before_create</tt>
18
  # * (-) <tt>create</tt>
19 20
  # * (5) <tt>after_create</tt>
  # * (6) <tt>after_save</tt>
21
  #
22
  # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
P
Pratik Naik 已提交
23
  # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each 
P
Pratik Naik 已提交
24
  # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
D
Initial  
David Heinemeier Hansson 已提交
25 26 27
  #
  # Examples:
  #   class CreditCard < ActiveRecord::Base
28
  #     # Strip everything but digits, so the user can specify "555 234 34" or
D
Initial  
David Heinemeier Hansson 已提交
29 30 31 32 33 34 35
  #     # "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
36 37 38 39 40 41
  #     before_create :record_signup
  #
  #     private
  #       def record_signup
  #         self.signed_up_on = Date.today
  #       end
D
Initial  
David Heinemeier Hansson 已提交
42 43 44 45
  #   end
  #
  #   class Firm < ActiveRecord::Base
  #     # Destroys the associated clients and people when the firm is destroyed
46 47 48
  #     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 已提交
49 50 51
  #
  # == Inheritable callback queues
  #
P
Pratik Naik 已提交
52
  # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
D
Initial  
David Heinemeier Hansson 已提交
53 54 55 56 57 58 59 60 61 62 63
  # 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
  #
64 65
  # 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 已提交
66 67 68 69 70 71 72 73 74 75
  # methods:
  #
  #   class Topic < ActiveRecord::Base
  #     def before_destroy() destroy_author end
  #   end
  #
  #   class Reply < Topic
  #     def before_destroy() destroy_readers end
  #   end
  #
76 77
  # 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 已提交
78
  # 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 已提交
79
  #
80 81 82 83
  # *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 已提交
84 85
  # == Types of callbacks
  #
86
  # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
D
Initial  
David Heinemeier Hansson 已提交
87
  # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
D
David Heinemeier Hansson 已提交
88
  # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
D
Initial  
David Heinemeier Hansson 已提交
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
  # 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 已提交
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 134 135
  #     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 已提交
136 137 138 139 140 141 142 143 144 145 146
  #     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 已提交
147
  #       record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
D
Initial  
David Heinemeier Hansson 已提交
148 149 150
  #     end
  #
  #     def after_save(record)
P
Pratik Naik 已提交
151
  #       record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
D
Initial  
David Heinemeier Hansson 已提交
152
  #     end
153
  #
D
David Heinemeier Hansson 已提交
154
  #     alias_method :after_find, :after_save
D
Initial  
David Heinemeier Hansson 已提交
155 156 157 158 159 160 161
  #
  #     private
  #       def encrypt(value)
  #         # Secrecy is committed
  #       end
  #
  #       def decrypt(value)
162
  #         # Secrecy is unveiled
D
Initial  
David Heinemeier Hansson 已提交
163 164 165 166 167 168 169 170 171 172
  #       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
  #
173
  # 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 已提交
174 175 176
  # inline callbacks can be stacked just like the regular ones:
  #
  #   class Topic < ActiveRecord::Base
177
  #     before_destroy 'self.class.delete_all "parent_id = #{id}"',
D
Initial  
David Heinemeier Hansson 已提交
178 179 180
  #                    'puts "Evaluated after parents are destroyed"'
  #   end
  #
181
  # == The +after_find+ and +after_initialize+ exceptions
D
Initial  
David Heinemeier Hansson 已提交
182
  #
183 184 185
  # 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
186
  # callback types will be called.
187
  #
188
  # == <tt>before_validation*</tt> returning statements
189
  #
190
  # 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 已提交
191
  # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
192 193
  # Nothing will be appended to the errors object.
  #
194
  # == Canceling callbacks
195
  #
196 197
  # 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
198
  # defined as methods on the model, which are called last.
199 200 201 202 203 204 205 206 207 208 209 210
  #
  # == 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 已提交
211
  module Callbacks
212
    extend ActiveSupport::Concern
213
    include ActiveSupport::NewCallbacks
214

215 216 217 218 219
    CALLBACKS = [
      :after_initialize, :after_find, :before_validation, :after_validation,
      :before_save, :after_save, :before_create, :after_create, :before_update,
      :after_update, :before_destroy, :after_destroy
    ]
D
Initial  
David Heinemeier Hansson 已提交
220

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

226
      define_callbacks :initialize, :find, :save, :create, :update, :destroy, :validation, "result == false"
D
Initial  
David Heinemeier Hansson 已提交
227 228
    end

229 230 231 232 233 234
    module ClassMethods
      def after_initialize(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        set_callback(:initialize, :after, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
235

236 237 238 239
      def after_find(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        set_callback(:find, :after, *(args << options), &block)
240
      end
D
Initial  
David Heinemeier Hansson 已提交
241

242 243 244
      def before_save(*args, &block)
        set_callback(:save, :before, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
245

246 247 248
      def around_save(*args, &block)
        set_callback(:save, :around, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
249

250 251 252 253 254 255
      def after_save(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        options[:if] = Array(options[:if]) << "!halted && value != false"
        set_callback(:save, :after, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
256

257 258 259
      def before_create(*args, &block)
        set_callback(:create, :before, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
260

261 262 263
      def around_create(*args, &block)
        set_callback(:create, :around, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
264

265 266 267 268 269 270
      def after_create(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        options[:if] = Array(options[:if]) << "!halted && value != false"
        set_callback(:create, :after, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
271

272 273 274
      def before_update(*args, &block)
        set_callback(:update, :before, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
275

276 277 278
      def around_update(*args, &block)
        set_callback(:update, :around, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
279

280 281 282 283 284 285
      def after_update(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        options[:if] = Array(options[:if]) << "!halted && value != false"
        set_callback(:update, :after, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
286

287 288 289
      def before_destroy(*args, &block)
        set_callback(:destroy, :before, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
290

291 292 293
      def around_destroy(*args, &block)
        set_callback(:destroy, :around, *args, &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
294

295 296 297 298 299 300
      def after_destroy(*args, &block)
        options = args.extract_options!
        options[:prepend] = true
        options[:if] = Array(options[:if]) << "!halted && value != false"
        set_callback(:destroy, :after, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
301

302 303 304 305 306 307 308 309
      def before_validation(*args, &block)
        options = args.extract_options!
        if options[:on]
          options[:if] = Array(options[:if])
          options[:if] << "@_on_validate == :#{options[:on]}"
        end
        set_callback(:validation, :before, *(args << options), &block)
      end
D
Initial  
David Heinemeier Hansson 已提交
310

311 312 313 314 315 316 317 318
      def after_validation(*args, &block)
        options = args.extract_options!
        options[:if] = Array(options[:if])
        options[:if] << "!halted"
        options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
        options[:prepend] = true
        set_callback(:validation, :after, *(args << options), &block)
      end
319

320 321 322 323 324 325 326
      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 已提交
327 328
    end

329 330 331 332 333 334
    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 已提交
335

336 337 338 339
    def create_with_callbacks #:nodoc:
      _run_create_callbacks do
        create_without_callbacks
      end
D
Initial  
David Heinemeier Hansson 已提交
340
    end
341
    private :create_with_callbacks
D
Initial  
David Heinemeier Hansson 已提交
342

343 344 345 346 347 348
    def update_with_callbacks(*args) #:nodoc:
      _run_update_callbacks do
        update_without_callbacks(*args)
      end
    end
    private :update_with_callbacks
349

350 351 352 353 354 355
    def valid_with_callbacks? #:nodoc:
      @_on_validate = new_record? ? :create : :update
      _run_validation_callbacks do
        valid_without_callbacks?
      end
    end
356

357 358 359 360 361
    def destroy_with_callbacks #:nodoc:
      _run_destroy_callbacks do
        destroy_without_callbacks
      end
    end
362

363 364 365 366
    def deprecated_callback_method(symbol) #:nodoc:
      if respond_to?(symbol)
        ActiveSupport::Deprecation.warn("Base##{symbol} has been deprecated, please use Base.#{symbol} :method instead")
        send(symbol)
D
Initial  
David Heinemeier Hansson 已提交
367
      end
368
    end
D
Initial  
David Heinemeier Hansson 已提交
369
  end
370
end