I'd like to propose adding another callback to the ActiveRecord lifecycle that triggers BEFORE the database transaction begins. Think of it as a corollary to after_commit and after_rollback.
*## Examples* This is an example resembling something we have to deal with in the Shopify codebase. This is how it *could *be written using this new callback. class CarrierService < ActiveRecord::Base validate :username, :password, presence: true validate :validate_credentials before_transaction :initiate_credential_validation def initiate_credential_validation logger.debug 'communicating with external carrier...' @response = external_service.validate(username, password) end def validate_credentials if ! @response.valid? # add validation error here end end end Imagine an interaction like this: carrier = CarrierService.new(username: 'jesse', password: 'password') carrier.save producing a log like this: communicating with external carrier... (0.1ms) begin transaction SQL (0.4ms) INSERT INTO "carrier_services" ("username", "password") VALUES (?, ?) [["username", "jesse"], ["password", "password"]] (1.9ms) commit transaction Note that the external communication happens before the transaction begins. We are doing this currently in our codebase, but it doesn't fit nicely into the traditional ActiveRecord callback cycle. Another obvious place where this would be beneficial is for models that represent assets / uploads that want to fetch the asset on creation but not inside the transaction. *## Motivation* The motivation for this addition is the same as for the after_commit callback. Some models need to communicate with external services in order to do validations / callbacks. To do so inside the DB transaction keeps that transaction open unnecessarily and wreaks havoc on the DB. *## Gotchas* The above example involving a single model being saved with the callback defined is the simple case. It can get more complicated in a scenario like the following: class Shop < ActiveRecord::Base end class ProductImage < ActiveRecord::Base before_begin :fetch_image end shop = Shop.find(1) shop.images << ProductImage.new(src: '...') shop.images << ProductImage.new(src: '...') # before opening the transaction to save the shop, we must look for autosave associations # and see if they have before_begin callbacks that need to be triggered shop.save We have prototyped a mostly functional supporting this relationship, so it can certainly be accomplished. This functionality would not play very nicely with the explicit transaction methods. For instance, when using .transaction this kind of callback could never be triggered reliably. shop = Shop.find(1) Shop.transaction do # we're already in a transaction at this point, so the callback has no hope of being triggered outside of this parent transaction Product.create(..., shop: shop) ProductImage.create(..., shop: shop) end We would have a little more context when using the #transaction method given that it's called on an instance that may have this callback defined (or have unsaved associations with this callback defined), but it could be used exactly as in the previous example and circumvent the process. So the callback would fit in nicely during the regular `save` lifecycle, but the explicit transaction methods would be a gotcha when using this feature. -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group. To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-core+unsubscr...@googlegroups.com. To post to this group, send email to rubyonrails-core@googlegroups.com. Visit this group at http://groups.google.com/group/rubyonrails-core. For more options, visit https://groups.google.com/d/optout.