I have some rails code in a controller action that has a transaction with a custom error.
When a speisfic error in a transaction happens I want to the transaction to rollback and render some json code, however I cannot seem to make the transaction rollback (it always commits) and then render some json. Here's my following code:
def controller_action
error_message = nil
begin
ActiveRecord::Base.transaction do
code_that_moifys_db_to_rollback
code_that_causes_custom_error
some_more_code_i_dont_want_to_run
end
rescue SomeCustomError::Error
error_message = 'this is an error message'
raise ActiveRecord::Rollback
end
rescue ActiveRecord::Rollback
# no rollback is getting triggered and data is now corrupt if json rendered
render :json => {error: true, message: error_message}
# however doing "raise 'xxxxx'" will cause a DB rollback in the transaction
end
How do I both:
I think I may be misunderstanding how rollbacks are triggered, the strangest thing is if I raise in the second rescue it will trigger the transaction to rollback.
I know I am too late to answer this question but some people might still be looking for an approach to its solution. So let's start with the problem and then head toward the solution.
In case you are in a hurry you can directly jump to the solution section.
Goal:
To render error response(other than saving object) and rollback transaction.
Problem context
In rails, if you have saving object errors(object.errors.present?) in a transaction then it will enter the rescue block (and you can handle raised exception in the rescue block) and your code will look something like this.
def controller_action
ActiveRecord::Base.transaction do
if object.save!
render_success_response('success message here')
else
render_error_response(object.errors)
end
rescue => e
render_error_response(e)
raise ActiveRecord::Rollback
end
I will define two methods here to render error and success responses that I will be using.
def render_error_response(errors)
render :json => {errors: errors}
end
def render_success_response(success_message)
render :json => {success_message: success_message}
end
Now in the above code, you have to manually rollback the transaction but if you want rails to rollback transaction for you then you can write the above code like this
def controller_action
begin
ActiveRecord::Base.transaction do
if object.save!
render_success_response('success message here')
else
render_error_response(object.errors)
end
rescue => e
render_error_response(e)
end
end
In the above code, rails will automatically rollback the transaction for you and will enter in the rescue block with an exception error.
Problem
But what if you have an errors array that comes in response to(call) a service or something and if errors are present in that array you want to render those errors and rollback the transaction? (as you know rails is not going to rollaback transaction for you this time as these errors are not saving object errors)
solution for versions below Rails 7
def controller_action
begin
ActiveRecord::Base.transaction do
response = ResponseFromService
if response[:errors].blank?
render_success_response('success message here')
else
render_error_response(response[:errors])
raise ActiveRecord::Rollback
end
rescue => e
render_error_response(e)
end
end
Solution for Rails 7
In rails 7 we got a new feature that's exactly for this problem and that's using the return keyword so in rails 7 code will look something like this
def controller_action
begin
ActiveRecord::Base.transaction do
response = ResponseFromService
if response[:errors].blank?
render_success_response('success message here')
else
return render_error_response(response[:errors])
end
rescue => e
render_error_response(e)
end
end
Warning: Note that the return keyword will silently(without raising an exception ) rollbacks the transaction so be careful while using it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With