I have a situation in which I would like a method to work within a transaction, but only if a transaction has not already been started. Here's a contrived example to distill what I'm talking about:
class ConductBusinessLogic
def initialize(params)
@params = params
end
def process!
ActiveRecord::Base.transaction do
ModelA.create_multiple(params[:model_a])
ModelB.create_multiple(params[:model_a])
end
end
end
class ModelA < ActiveRecord::Base
def self.create_multiple(params)
# I'd like the below to be more like "ensure_transaction"
ActiveRecord::Base.transaction do
params.each { |p| create(p) }
end
end
end
class ModelB < ActiveRecord::Base
def self.create_multiple(params)
# Again, a transaction here is only necessary if one has not already been started
ActiveRecord::Base.transaction do
params.each { |p| create(p) }
end
end
end
Basically, I don't want these to act as nested transactions. I want the .create_multiple
methods to only start transactions if they are not already called within a transaction, such as through ConductBusinessLogic#process!
. If the model methods are called by themselves, they should start their own transaction, but if they are already being called inside a transaction, as through ConductBusinessLogic#process!
, they should not nest a sub-transaction.
I don't know of a way in which Rails provides this out of the box. If I run the above code as-is and a rollback is triggered by one of the model methods, the whole transaction will still go through because the sub-transaction swallows the ActiveRecord::Rollback
exception. If I use the requires_new
option on the sub-transactions, savepoints will be used to simulate nested transactions, and only that sub-transaction will actually be rolled back. The behavior I would like would be something to the effect of ActiveRecord::Base.ensure_transaction
, such that a new transaction is started only if there isn't already an outer transaction, so that any sub-transaction can trigger a rollback on the entire outer transaction. This would allow these methods to be transactional on their own, but defer to a parent transaction if there is one.
Is there a built-in way to achieve this behavior, and if not, is there a gem or patch that will work?
Transactions in ActiveRecordEvery database operation that happens inside that block will be sent to the database as a transaction. If any kind of unhandled error happens inside the block, the transaction will be aborted, and no changes will be made to the DB.
Rails transactions are a way to ensure that a set of database operations will only occur if all of them succeed. Otherwise they will rollback to the previous state of data.
How about just adding a create_multiple_without_transaction
method to your ModelA
and ModelB
classes? which would look something like this:
class ConductBusinessLogic
def initialize(params)
@params = params
end
def process!
ActiveRecord::Base.transaction do
ModelA.create_multiple_without_transaction(params[:model_a])
ModelB.create_multiple_without_transaction(params[:model_a])
end
end
end
class ModelA < ActiveRecord::Base
def self.create_multiple(params)
# I'd like the below to be more like "ensure_transaction"
ActiveRecord::Base.transaction do
self.create_multiple_without_transaction(params)
end
end
def self.create_multiple_without_transaction(params)
params.each { |p| create(p) }
end
end
then your regular create_multiple
would work as before, but for cases were you don't need a transaction you would just call create_multiple_without_transaction
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With