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.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs