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.