I’ve had a few opportunities lately to work with transactions in Rails. Transactions are useful when you have more than one model that needs to be saved in one pass, but you don’t want to have to deal with deleting one if the other doesn’t save successfully, etc. Basically, if you save a model within a transaction and it fails, any other models that were saved in that transaction will be rolled back.
Here’s a quick example:
So, if either @post.save!
or @tag.save!
fails, the entire
transaction will be rolled back. Notice that I’m using save!
and not
save
. This is because you have to throw an exception for the
transaction block to realize the the transaction has failed. The problem
with this, though, is that the transaction doesn’t automatically rescue
the error. You have to do that yourself. I’ll leave that as an exercise
for the reader. Just kidding! Check this out:
Note: check out this best practice for an explanation of the rescue syntax: Don’t rescue Exception, rescue StandardError
Notice anything wrong? I didn’t at first, but what you may not notice is
that if the post is created successfully, but the tag is not, the
following quirkiness ensues: the post is rolled back, but the instance
retains the id=1
attribute. This causes the respond_with @post
to
fail because there isn’t a Post with id=1
— it got rolled back!
So what do we do to compensate?
I don’t know the best way to deal with this, so if you have better ideas, please leave them in the comments and I’ll update this post. Having said that, here’s an idea that works:
Running @post.valid?
on the newly instantiated object will build all
of the model’s errors for display on the form, but it will no longer
have the id that was messing things up before.
I’m not sure if I have to use render :new
because I’m using Rails 4.0
or if respond_with
always renders :index
when the creation fails.
I’ll do some more research and update the post later.