How ActiveRecord::Rollback behaves in nested begin-rescue blocks - ruby-on-rails

I have below code
ActiveRecord::Base.transaction do
begin
account.save
# outer statement
begin
user.save
# inner statement
rescue StandardError
raise ActiveRecord::Rollback
end
rescue StandardError
raise ActiveRecord::Rollback
end
end
If there is an exception in 'inner statement', only 'user' will be rollbacked, right? 'account' won't be rollbacked in that case, isn't it?

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

How to debug inside a code block without skipping it

I'm attempting to debug my code, in order to see if #new_participant is instantiated, but when I place binding.pry around it as displayed below, it hops over the block, and placing the debugger within the block obviously doesn't work either. How do I debug this?
def create_participant!(case_file)
binding.pry
params = participant_params(case_file.case_file.id)
ActiveRecord::Base.transaction do
#new_participant = participant_clazz.create!(params)
assign_private_infos!(participant_id: new_participant.id)
binding.pry
end
call_link_participant_job!
end
You're calling create! which will raise an ActiveRecord::RecordInvalid exception if the record is not valid. Exceptions immediately halt the script execution and Ruby goes up the call stack until a rescue is found (or not).
ActiveRecord::Transactions wraps the block in a rescue which triggers a rollback and then propagates the exception.
Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you should be ready to catch those in your application code.
If you want to run code before the rollback you need to rescue the exception inside the block:
def create_participant!(case_file)
binding.pry
params = participant_params(case_file.case_file.id)
ActiveRecord::Base.transaction do
begin
#new_participant = participant_clazz.create!(params)
assign_private_infos!(participant_id: new_participant.id)
rescue ActiveRecord::RecordInvalid => e
binding.pry
raise e # re-raise the exception to trigger a rollback
end
call_link_participant_job!
end
end
Or you can rescue the exception after the rollback:
def create_participant!(case_file)
binding.pry
params = participant_params(case_file.case_file.id)
begin
ActiveRecord::Base.transaction do
#new_participant = participant_clazz.create!(params)
assign_private_infos!(participant_id: new_participant.id)
call_link_participant_job!
end
rescue ActiveRecord::RecordInvalid => e
binding.pry
end
end

raise ActiveRecord::Rollback in after_save doesn't rollback the save

Inside the after_save method, I have:
def sync
status = Timeout::timeout(1) {
sleep(2)
}
rescue StandardError => e
puts "inside rescue"
errors[:base] << e.message.to_s
ApsLogger.fatal(e)
raise ActiveRecord::Rollback
# raise ActiveRecord::RecordInvalid.new(self)
end
I see inside rescue gets printed out, but the record is still being modified in the DB.
I tried with raise ActiveRecord::RecordInvalid.new(self), still doesn't work.
I expect to see the record not modified in the DB, and an error message show up in the UI.

Handling exceptions in rails

I am bit unclear about exception handling while using Active record transactions in rails. I have seen many of them using,
Method: 1
def update
ActiveRecord::Base.transaction do
begin
# Some logic
rescue StandardError => e
raise ActiveRecord::Rollback
end
end
end
and have seen the below logics in many of the places.
Method:2
def update
ActiveRecord::Base.transaction do
if object.update(update_params)
# success
else
# error handling
end
end
rescue => e
# error handling
end
What I think is the second method itself is enough. I thought that, Transaction itself will rollback if anything unexpected happens or any logical error inside the transaction and we can catch them and do whatever we want. Is catching exception inside the transaction and raising Rollback manually needed anywhere?. What is the difference between both the methods and in which case?
You don't need to manually to rollback the transaction the code below should be good enough
def update
ActiveRecord::Base.transaction do
foo.update(foo_update_params)
end
rescue ActiveRecord::RecordInvalid
# Handle your exception here
end
Have a look here for a better explanation.

ActiveRecord rollback and return RecordInvalid message

I want to save multiple objects, and rollback all if any of them fail. But I also want to render the ActiveRecord::RecordInvalid message so the user knows why it didn't save. How do I do this?
def save_multiple_things_or_error
ActiveRecord::Base.transaction do
thing_one.save!
thing_two.save!
rescue ActiveRecord::RecordInvalid => exception
# exception.message is what I want to render
raise ActiveRecord::Rollback
end
end
This doesn't work for a few reasons. I believe the rescue should be in a begin end block, but then if I raise the rollback, I lose the RecordInvalid exception.
you could try this one:
begin
ActiveRecord::Base.transaction do
thing_one.save!
thing_two.save!
end
rescue => e
raise ActiveRecord::Rollback
end
this works fine for my situation

Is it possible to re-raise an ActiveRecord::Rollback exception from inside a transaction block?

In my rails app, I have a transaction block in a controller's create action in which I attempt to create two records.
def create
ActiveRecord::Base.transaction do
#foo = Foo.new(params[:foo].except(:bar))
raise ActiveRecord::Rollback unless #foo.save
#bar = Bar.new(params[:foo][:bar])
raise ActiveRecord::Rollback unless #bar.save
end
end
I'd like to be able to rescue from the Rollback so I can return an error indicating which save failed.
rescue ActiveRecord::Rollback => e
#foo.errors = e if #foo.errors.blank?
ensure
respond_with #foo
end
However, I never get into the rescue block. I assume this is because, as the rails documentation states, the transaction block eats the Rollback exception and doesn't re-raise it. Is there a way to force this exception to be re-raised?

Resources