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:
- begin ActiveRecord::Base.transaction do
-
@first_model = FirstModel.new(params[:first_model])
-
@first_model.save!
-
@second_model = SecondModel.new(:user =>
current_user, :first_model => @first_model)
-
@second_model.save!
- end
- rescue Exception => @ex
-
handle the exception
- 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:
- begin
- FirstModel.transaction do
- SecondModel.transaction do
-
@first_model = FirstModel.new(params[:first_model])
-
@first_model.save!
-
@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:
-
@first_model = FirstModel.new(params[:first_model])
-
@first_model.save!
-
@second_model = SecondModel.new(:user =>
current_user, :first_model => @first_model)
-
@second_model.save!
- end
- rescue Exception => @ex
-
handle the exception
- 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.