How can I handle multiple models saves in a single transactional

Hi all.

I need to create two model in a single transaction: if the first
fails, the transaction should be rolled back; if the second fails, the
transaction should be rolled back.

This is my code:

  1. begin ActiveRecord::Base.transaction do
  2. @first_model = FirstModel.new(params[:first_model])
  3. @first_model.save!
  4. @second_model = SecondModel.new(:user =>
    current_user, :first_model => @first_model)
  5. @second_model.save!
  6. end
  7. rescue Exception => @ex
  8. handle the exception

  9. end

The global rollback works only if the “@first_model.save!” fails.
If the “@second_model.save!” fails, the global transaction isn’t
rolled back, and the first_model is still saved on database.

I’ve also tried using nested transactions:

  1. begin
  2. FirstModel.transaction do
  3. SecondModel.transaction do
  4. @first_model = FirstModel.new(params[:first_model])
    
  5. @first_model.save!
    
  6. @second_model = SecondModel.new(:user =>
    

current_user, :first_model => @first_model)
7. @second_model.save!
8. end
9. end
10.rescue Exception => @ex
11. # handle the exception
12.end

but also this solutions shows the same problems as above.

How can I solve my issue?

Is there a way to define a sort of global transaction in Rails?

Thanks, Stefano

On 11 Apr 2008, at 11:25, xponrails wrote:

  1. @first_model = FirstModel.new(params[:first_model])
  2. @first_model.save!
  3. @second_model = SecondModel.new(:user =>
    current_user, :first_model => @first_model)
  4. @second_model.save!
  5. end
  6. rescue Exception => @ex
  7. handle the exception

  8. end

The global rollback works only if the “@first_model.save!” fails.
If the “@second_model.save!” fails, the global transaction isn’t
rolled back, and the first_model is still saved on database.

Are testing this from a unit test or in development mode ?

I’ve also tried using nested transactions:
(rails doesn’t support nested transactions. the second and following
transactions are basically no-ops)

Fred

I’ve tested this in development mode, breaking the second save! using:

@second_model = SecondModel.new(:user => “mickeymouse”, :first_model
=> @first_model)
@second_model.save!

This code throws an exception due to the string “mickeymouse” and this
second save is rolled back. But the first save isn’t rolled back and
the @first_model is saved in my database.
PS: I’m using MySql 5 and Rails 2.0.2

this is the except of my log:

||[4;36;1mSQL (0.000000)||[0m ||[0;1mBEGIN||[0m
||[4;35;1mFirstModel Columns (0.000000)||[0m ||[0mSHOW FIELDS FROM
fisrt_models||[0m
||[4;36;1mFirstModel Create (0.000000)||[0m ||[0;1mINSERT INTO
first_models (name) VALUES(‘tsdf’)||[0m
||[4;35;1mSecondModel Columns (0.000000)||[0m ||[0mSHOW FIELDS FROM
second_models||[0m
||[4;36;1mSQL (0.000000)||[0m ||[0;1mROLLBACK||[0m
User expected, got String

I am no genus, but a couple things come to mind.

It appears that save! throws an exception upon failure and is
supposed to trigger a rollback. You are rescuing that exception.
Maybe try it without the rescue.

The transaction is only on the @first_model which it appears is not
cascading to the @second_model
Maybe instead of save!, use the save message which returns false
instead of raising an exception.

@second_model = SecondModel.new(:user => “mickeymouse”, :first_model
=> @first_model)
@first_model.rollback unless @second_model.save

Good luck

On 11 Apr 2008, at 12:40, xponrails wrote:

PS: I’m using MySql 5 and Rails 2.0.2

That’s odd. I suspect what is happening (check this in your logs) is
that the first save is being wrapped in a transaction, but since you
already have a transaction going, starting that transaction implicitly
commits the first. However rails should notice that you are already in
a transaction and not do this.

Fred

I found this

Wes G.
From: Wes G. [email protected]
Date: Fri, 19 Jan 2007 18:54:09 +0100
Local: Fri, Jan 19 2007 10:54 am
Subject: AR transaction method doesn’t rollback in all cases

All,

I think I’m seeing the behavior specified in this post to Rails-core:
http://www.ruby-forum.com/topic/81605

I believe that this post is an example of the behavior I’ll describe
as
well:
http://www.ruby-forum.com/topic/83260

I’d like to verify the following assertion:

Assertion: Within a transaction block, a rollback will not occur
automatically unless an exception is thrown from within that top-level
block explicitly. If there are begin/rescue blocks within the
transaction block that catch exceptions, then the transaction block
will
not be able to detect these exceptions and will not rollback.

I’m seeing this on a multi-object update, so I am now using save
(instead of save!) and rollback_db_transaction explicitly by detecting
whether each object’s error array is non-empty.

Thanks,
Wes


Posted via http://www.ruby-forum.com/.

Hi.
For now I’ve resolved using this code:

begin
@first_model = FirstModel.new(params[:first_model])
@second_model = SecondModel.new(:user =>
current_user, :first_model => @first_model)
if @first_model.save and @second_model.save
logger.warn “saved”
else
logger.warn “this should rollback”
end
rescue Exception => e
end

I’ve placed in a single if statement all the saves “if
@first_model.save and @second_model.save” and all works, but I really
don’t like this approach.
And I don’t understand WHY Rails and ActiveRecords cannot let the
developer to handle complex transactions by himself.