Ensuring a transaction within a method with ActiveRecord
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?
No comments:
Post a Comment