Rails Not Rolling Back Transaction After Failed Save()

Rails not rolling back transaction after failed save()

Adding an item to the collection saves it immediately (unless the user is unsaved).
The call to save creates its own transaction and that is what is rolled back, not the transaction in which the item is saved

You could force everything into the same transaction by creating one explicitly.

begin
User.transaction do
@user.items << item
@user.save!
render :json => {}, :status => :ok
end
rescue ActiveRecord::RecordInvalid
render :json => {:status => :error, :errors => item.errors}, :status => :bad_request
end

Why does my rails rollback when I try to user.save?

Your user model probably has validations which are not satisfied. Since you've not posted those I'm unable to really solve your question. To make live easier you can debug why your user isn't willing to save.

Try running

user.errors.full_messages

which should give you a hint what's going wrong.

ActiveRecord transaction is not rolling back whole transaction

to have your transaction rolled back in case of error, replace t.save with t.save!

This would result in:

respond_to do |format|
begin
ActiveRecord::Base.transaction do
@tickets.each &:save!
format.html { redirect_to tickets_path, notice: "#{number} #{"ticket".pluralize(number)} successfully created." }
format.json { render :show, status: :created, location: tickets_path }
end
rescue ActiveRecord::ActiveRecordError => e
format.html { render :new, notice: "Some tickets have errors, check the serial number range" }
format.json { render json: e.message, status: :unprocessable_entity }
end
end

Automatic rollback without any error?

You can check the errors after a save or valid?

billing_name = BillingName.new
billing_name.save # or billing_name.valid?
puts billing_name.errors.inspect

Rails transaction not rolling back

This was just a testing issue with rspec because you're already executing in a transaction in the test. It's the same nested transaction issue discussed here.
In order to get the test to work you need to add…

requires_new: true

to the transaction call.

This fixed the test.

def process_photos(photos)
ActiveRecord::Base.transaction(requires_new: true) do
begin
photos.each do |photo|
Photo.create!(creator_user: @user, buyer: @buyer, url: photo['url'])
end
rescue
raise ActiveRecord::Rollback
end
end
end

Important: Rollbacks will only work if your database engine supports them! For example MySQL with MyISAM doesn't support transactions, while MySQL with Inno DB does support them.

How to rollback all transactions in transaction block on error in Ruby on Rails

This is insanely overcomplicated and you have completely missunderstood how to use nested attributes:

class MissionsController
def create
@mission = Mission.new(mission_attributes)
if @mission.save
redirect_to @mission
else
render :new
end
end

...

private

def mission_params
params.require(:mission)
.permit(
:param_1, :param_2, :param3,
addresses_attributes: [:foo, :bar, :baz],
phones_attributes: [:foo, :bar, :baz],
camera_spec_attributes: [:foo, :bar, :baz],
)
end
end

All the work is actually done automatically by the setters declared by accepts_nested_attributes. You just pass the hash or array of hashes of whitelisted parameters to it and let it do its thing.

You can prevent the parent object from being saved if the child object is invalid by using validates_associated:

class Mission < ApplicationRecord
# ...
validates_associated :addresses
end

This just adds the error key “Phone is invalid” to the errors which isn't very user friendly. If you want to display the error messages per nested record you can get the object wrapped by the form builder when using fields_for:

# app/shared/_errors.html.erb
<div id="error_explanation">
<h2><%= pluralize(object.errors.count, "error") %> prohibited this <%= object.model_name.singular %> from being saved:</h2>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
...
<%= f.fields_for :address_attributes do |address_fields| %>
<%= render('shared/errors', object: address_fields.object) if address_fields.object.errors.any? %>
<% end %>

How ActiveRecord::Rollback behaves in nested begin-rescue blocks

In your code there is a single database transaction, and it is all-or-nothing. Rolling back a transaction will roll back all the changes made in that transaction, regardless of where you issue the rollback.

You can nest transactions as well, but be wary that by default transactions are squished together, so even if you add a second transaction inside first transaction:

ActiveRecord::Base.transaction do
begin
account.save
# outer statement
ActiveRecord::Base.transaction do
begin
user.save
# inner statement
rescue StandardError
raise ActiveRecord::Rollback
end
end
rescue StandardError
raise ActiveRecord::Rollback
end
end

This will still result in the single transaction, and rollback will cancel all the changes.

To ask for a real subtransaction, you need to add request_new: true to the inner transaction:

ActiveRecord::Base.transaction do
begin
account.save
# outer statement
ActiveRecord::Base.transaction(require_new: true) do
begin
user.save
# inner statement
rescue StandardError
raise ActiveRecord::Rollback
end
end
rescue StandardError
raise ActiveRecord::Rollback
end
end

However, at the moment the only database supporting true nested transaction is MS-SQL. Rails at the moment handles this using save points - so don't be confused by the logs.



Related Topics



Leave a reply



Submit