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
What Orm to Use in One Process Multiple Db Connections Sinatra Application
Updating from Rails 4.0 to 4.1 Gives SASS-Rails Railties Version Conflicts
Rails Generate Controller Gives Me Load Error
Rotate Bits Right Operation in Ruby
What's the Best Way to Return an Enumerator::Lazy When Your Class Doesn't Define #Each
More Concise Version of Max/Min Without the Block
Ruby Ternary Operator Structure
Fileutils.Mv Throwing Invalid Char \302 and \255 Exception
Rails 4 Many to Many Association Not Working
How to Capture a Part of a Screen Using Ruby on Windows
Ruby on Rails Active Admin Has_Many Changing Dropdown to Use a Different Column
How to Include Ё in [А-Я] Regexp Char Interval
Building a Simple Search Form in Rails
What's the Difference Between the Ruby Irb Prompt Modes
How to Replace the Characters in a String
Best Way to Find_Or_Create_By_Id But Update the Attributes If the Record Is Found