1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
|
# frozen_string_literal: true
module ActiveRecord
# = Active Record \Callbacks
#
# \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
# before or after a change in the object state. This can be used to make sure that associated and
# dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
# to massage attributes before they're validated (by overwriting +before_validation+).
# As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
#
# * (-) <tt>save</tt>
# * (-) <tt>valid</tt>
# * (1) <tt>before_validation</tt>
# * (-) <tt>validate</tt>
# * (2) <tt>after_validation</tt>
# * (3) <tt>before_save</tt>
# * (4) <tt>before_create</tt>
# * (-) <tt>create</tt>
# * (5) <tt>after_create</tt>
# * (6) <tt>after_save</tt>
# * (7) <tt>after_commit</tt>
#
# Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
# Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
# <tt>after_rollback</tt>.
#
# Additionally, an <tt>after_touch</tt> callback is triggered whenever an
# object is touched.
#
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
# are instantiated as well.
#
# There are nineteen callbacks in total, which give a lot of control over how to react and prepare for each state in the
# Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
#
# Examples:
# class CreditCard < ActiveRecord::Base
# # Strip everything but digits, so the user can specify "555 234 34" or
# # "5552-3434" and both will mean "55523434"
# before_validation(on: :create) do
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
# end
# end
#
# class Subscription < ActiveRecord::Base
# before_create :record_signup
#
# private
# def record_signup
# self.signed_up_on = Date.today
# end
# end
#
# class Firm < ActiveRecord::Base
# # Disables access to the system, for associated clients and people when the firm is destroyed
# before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
# before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
# end
#
# == Inheritable callback queues
#
# Besides the overwritable callback methods, it's also possible to register callbacks through the
# use of the callback macros. Their main advantage is that the macros add behavior into a callback
# queue that is kept intact through an inheritance hierarchy.
#
# class Topic < ActiveRecord::Base
# before_destroy :destroy_author
# end
#
# class Reply < Topic
# before_destroy :destroy_readers
# end
#
# 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.
#
# *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.
#
# == Types of callbacks
#
# There are three types of callbacks accepted by the callback macros: method references (symbol), callback objects,
# inline methods (using a proc). \Method references and callback objects are the recommended approaches,
# inline methods using a proc are sometimes appropriate (such as for creating mix-ins).
#
# 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_by(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
# 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_initialize, :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 to be 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
# 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)
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
# end
#
# def after_save(record)
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
# end
#
# alias_method :after_initialize, :after_save
#
# private
# def encrypt(value)
# # Secrecy is committed
# end
#
# def decrypt(value)
# # Secrecy is unveiled
# end
# end
#
# == <tt>before_validation*</tt> returning statements
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
# If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
# If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
# the associated action are cancelled.
# \Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
# methods on the model, which are called last.
#
# == Ordering callbacks
#
# Sometimes application code requires that callbacks execute in a specific order. For example, a +before_destroy+
# callback (+log_children+ in this case) should be executed before records in the +children+ association are destroyed by the
# <tt>dependent: :destroy</tt> option.
#
# Let's look at the code below:
#
# class Topic < ActiveRecord::Base
# has_many :children, dependent: :destroy
#
# before_destroy :log_children
#
# private
# def log_children
# # Child processing
# end
# end
#
# In this case, the problem is that when the +before_destroy+ callback is executed, records in the +children+ association no
# longer exist because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback was executed first.
# You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
#
# class Topic < ActiveRecord::Base
# has_many :children, dependent: :destroy
#
# before_destroy :log_children, prepend: true
#
# private
# def log_children
# # Child processing
# end
# end
#
# This way, the +before_destroy+ is executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
#
# Also, there are cases when you want several callbacks of the same type to
# be executed in order.
#
# For example:
#
# class Topic < ActiveRecord::Base
# has_many :children
#
# after_save :log_children
# after_save :do_something_else
#
# private
# def log_children
# # Child processing
# end
#
# def do_something_else
# # Something else
# end
# end
#
# In this case the +log_children+ is executed before +do_something_else+.
# This applies to all non-transactional callbacks, and to +before_commit+.
#
# For transactional +after_+ callbacks (+after_commit+, +after_rollback+, etc), the order
# can be set via configuration.
#
# config.active_record.run_after_transaction_callbacks_in_order_defined = false
#
# When set to +true+ (the default from \Rails 7.1), callbacks are executed in the order they
# are defined, just like the example above. When set to +false+, the order is reversed, so
# +do_something_else+ is executed before +log_children+.
#
# == \Transactions
#
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
# or {#destroy}[rdoc-ref:Persistence#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}[rdoc-ref:Persistence#save] will raise such exception
# instead of quietly returning +false+.
#
# == Debugging callbacks
#
# The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
# <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
# defines what part of the chain the callback runs in.
#
# To find all callbacks in the +before_save+ callback chain:
#
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
#
# Returns an array of callback objects that form the +before_save+ chain.
#
# To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
#
# Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
#
# Returns true or false depending on whether the proc is contained in the +before_save+ callback chain on a Topic model.
#
module Callbacks
extend ActiveSupport::Concern
CALLBACKS = [
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
]
module ClassMethods
include ActiveModel::Callbacks
##
# :method: after_initialize
#
# :call-seq: after_initialize(*args, &block)
#
# Registers a callback to be called after a record is instantiated. See
# ActiveRecord::Callbacks for more information.
##
# :method: after_find
#
# :call-seq: after_find(*args, &block)
#
# Registers a callback to be called after a record is instantiated
# via a finder. See ActiveRecord::Callbacks for more information.
##
# :method: after_touch
#
# :call-seq: after_touch(*args, &block)
#
# Registers a callback to be called after a record is touched. See
# ActiveRecord::Callbacks for more information.
##
# :method: before_save
#
# :call-seq: before_save(*args, &block)
#
# Registers a callback to be called before a record is saved. See
# ActiveRecord::Callbacks for more information.
##
# :method: around_save
#
# :call-seq: around_save(*args, &block)
#
# Registers a callback to be called around the save of a record. See
# ActiveRecord::Callbacks for more information.
##
# :method: after_save
#
# :call-seq: after_save(*args, &block)
#
# Registers a callback to be called after a record is saved. See
# ActiveRecord::Callbacks for more information.
##
# :method: before_create
#
# :call-seq: before_create(*args, &block)
#
# Registers a callback to be called before a record is created. See
# ActiveRecord::Callbacks for more information.
##
# :method: around_create
#
# :call-seq: around_create(*args, &block)
#
# Registers a callback to be called around the creation of a record. See
# ActiveRecord::Callbacks for more information.
##
# :method: after_create
#
# :call-seq: after_create(*args, &block)
#
# Registers a callback to be called after a record is created. See
# ActiveRecord::Callbacks for more information.
##
# :method: before_update
#
# :call-seq: before_update(*args, &block)
#
# Registers a callback to be called before a record is updated. See
# ActiveRecord::Callbacks for more information.
##
# :method: around_update
#
# :call-seq: around_update(*args, &block)
#
# Registers a callback to be called around the update of a record. See
# ActiveRecord::Callbacks for more information.
##
# :method: after_update
#
# :call-seq: after_update(*args, &block)
#
# Registers a callback to be called after a record is updated. See
# ActiveRecord::Callbacks for more information.
##
# :method: before_destroy
#
# :call-seq: before_destroy(*args, &block)
#
# Registers a callback to be called before a record is destroyed. See
# ActiveRecord::Callbacks for more information.
##
# :method: around_destroy
#
# :call-seq: around_destroy(*args, &block)
#
# Registers a callback to be called around the destruction of a record.
# See ActiveRecord::Callbacks for more information.
##
# :method: after_destroy
#
# :call-seq: after_destroy(*args, &block)
#
# Registers a callback to be called after a record is destroyed. See
# ActiveRecord::Callbacks for more information.
end
included do
include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :touch, only: :after
define_model_callbacks :save, :create, :update, :destroy
end
def destroy # :nodoc:
@_destroy_callback_already_called ||= false
return true if @_destroy_callback_already_called
@_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
@_association_destroy_exception = e
false
ensure
@_destroy_callback_already_called = false
end
def touch(*, **) # :nodoc:
_run_touch_callbacks { super }
end
def increment!(attribute, by = 1, touch: nil) # :nodoc:
touch ? _run_touch_callbacks { super } : super
end
private
def create_or_update(**)
_run_save_callbacks { super }
end
def _create_record
_run_create_callbacks { super }
end
def _update_record
_run_update_callbacks { record_update_timestamps { super } }
end
end
end
|