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

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.

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

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

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

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

I found this

Wes Gamble

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.