I'd like to add a begin...rescue block to one of my controllers create method, in order to log better info and construct the correct error message to return to the client. Does the rescue in any way 'interrupt' the rollback process?
I'm assuming rails automatically does a rollback. When does it happen? Has it already happened by the time I get in the rescue clause?
I'm using mySQL on Dreamhost and I think they use innoDB.
I've been experimenting with this. It seems like if your rescue catches the exception that would have caused the rollback, the part of the transaction that already happened gets committed. In my case, I want the database rolled back to the way it was before the transaction started, but I still want to handle the exception.
I ended up with this:
self.transaction do
first_operation
begin
operation_that_might_violate_db_constraint
rescue ActiveRecord::RecordNotUnique
#deal with the error
raise ActiveRecord::Rollback #force a rollback
end
end
The raise ActiveRecord::Rollback part makes sure the transaction gets completely rolled back. Without it, the changes from first_operation would end up getting committed.
The ActiveRecord::Rollback is a special kind of exception that doesn't bubble above the level of the transaction, so you won't end up with an uncaught exception that renders the error page.
I'm not sure this is the gold-standard way of doing this, but it seems to work.
Just using begin...rescue isn't enough to rollback a transaction. You need to use:
ModelName.transaction do
end
This is done explicitely on a call to save, so that all of your callbacks are executed together. What exceptions are you catching in your rescue block? What are you responding to? What kind of errors?
Rollback not be processed.
ex:
create_table "helps", :force => true do |t|
t.string "title", :null => false
t.text "content"
end
#Rails console
Help.transaction do
Help.create! title: "aaa"
begin
Help.create! content: "111"
rescue
p "create error."
end
Help.create! title: "bbb"
end
#get this
>> "create error."
Help.count
>> 2
You can also try my answer for rollback, catch and rendering for your create method using ActiveRecord::Base.transaction:-
Click Here
Thanks
Related
I have a simple exception handler as follows
begin
# code
rescue Exception
# Write to database
raise
end
The write to database is rolled back if raise is called. Is what I'm attempting to do possible?
Edit
Write to database does the following
Question.create(
notification_id: 1,
text: 'test'
)
Very simple.
You cannot rollback unless you use a transactions like follows
raise ActiveRecord::Rollback, "Call tech support!"
In your case, May be you have validated attributes in model (Question) and it get failed.
You can check errors like :
questions=Questions.new(...)
errors = questions.errors.full_messages if questions.invalid?
ActiveRecord::Base.transaction do
do_this
Something.after_commit.action
do_that
end
# Something.action is fired/run in case no exceptions in transaction
How can one achieve this?
NOTE: one doesn't see where the transaction starts and ends (I mean transaction do and end)
As I understand your question you want to define code that should be run after the transaction inside a transation? But you do not want to run it straight away?
If this is the case it can be achieved with a proc. Info about procs.
So the code would be something like:
no_exceptions = true
ActiveRecord::Base.transaction do
do_this
after_code = Proc.new do |no_e|
# Code defining how to act no_e gives you info if exceptions where fired.
end
do_that
rescue Exception => e
no_exceptions = false
end
after_code.call(no_exceptions)
I hope this was what you where looking for.
You must use this pattern, with the save! (that raises exception on error):
begin
do_this_before_update_the_db
ActiveRecord::Base.transaction do
update_1_db_if_no_fails
update_2_db_if_no_fails
...
update_n_db_if_no_fails
end
DO-THIS-AFTER-COMMIT-ONLY-IF-NO-EXCEPTIONS
rescue Exception => exception
do_this_if_some_update_fails # depending on the exception you must raise again from here.
ensure
do_this_always_fail_or_no # this runs after commit
end
You can't put after_commit inside the transaction, because this run inside the transaction, before the commit is done.
All that is inside the transaction block runs with one commit at the end of the block. All callbacks runs inside this transaction. And all indented transactions runs inside this transaction (with only one commit). You can found detailed information here.
You must see that at the log, with only one commit at the end.
Also you can have this pattern on update_2_db_if_no_fails, with an ensure block that runs always fail or no the update_2. This run inside the first transaction, but is something different of your model after_commit callback.
So I am trying to achieve atomicitiy in while doing my saves (which are essentially updates on each rows).
params[:player_types].each do |p_type_params|
if p_type_params[:id]
player = #player_types.find(p_type_params[:id])
player.assign_attributes(p_type_params)
#player_types << player
end
end
ActiveRecord::Base.transaction do
#player_types.each do |player_type|
if player_type.save
"DO Something.."
else
"DO something else.."
errors = true
end
end
end
Inspite of saving withing the transaction block, I can see partial saves also i.e. one of the rows get updated and the erroneous one does not (obviously) where as I would have wanted the updated row to be rolled back since there was atleast one row that could not be updated due to an error. Is my interpretation of transaction block correct in this case? How do I achieve an atomic save in my case?
EDIT: The model validates for uniqueness of one of the columns, which would be the reason for failing to update in the Database at this point.
You need to raise an error inside your transaction block to abort the transaction; setting errors doesn't impact the transaction.
For instance:
ActiveRecord::Base.transaction do
#player_types.each do |player_type|
if player_type.save
"DO Something.."
else
"DO something else.."
raise "save failed!"
end
end
end
The more conventional way of doing this, of course, is to use save! which raises an exception for you when it fails:
ActiveRecord::Base.transaction do
#player_types.each do |player_type|
player_type.save!
end
end
If you really need to "DO Something" on failure (besides aborting the transaction), you'll have to use the first method.
I have the following method in my model:
class Task < ActiveRecord::Base
def update_completed_task(task_params, completed_material_params)
puts 'in update_completed_task method'
transaction do
begin
puts 'inside transaction'
self.task_finished
puts 'after task finished'
self.update_attributes!(task_params)
puts 'after update_attributes'
if completed_material_params
completed_material_params.each do |key, value|
#completed_material = CompletedMaterial.where("identity = ?", value).first
#completed_material.task = self
#completed_material.save
end
end
puts 'affter loop'
UserNotification.freelancer_has_submitted_documents(self.schedule.project, self)
puts 'after user notification change'
rescue
puts 'in rescue again yolo gandi rollback'
end
end
end
end
I am new to transactions in rails, but my understanding was that if one of the database interactions failed, the whole transaction would be rolled back. In the code above, the line:
self.update_attributes(task_params)
is failing, so the database update that occurs from self.task_finished on the line before should be rolled back. For some reason, it is not rolled back.
For background information, although i don't think it should make a difference, the "self.task_finished" line uses the state_machine gem to change the state of task. It should still rollback though. What is wrong with my transaction
You're misunderstanding what constitutes "failure"; record validations aren't "failure", they're a normal part of using your app, and they don't force any kind of database rollback.
You need to either explicitly cancel the transaction, or leave the transaction block via an exception, for the transaction to fail. Currently, you're successfully reaching the end of the transaction, so everything is happily committed to the database.
As suggested already, the best solution is update_attributes! which makes your validations into real failure by throwing an exception. As for your problem with update_attributes!...
tried that and it did do the rollback, but it also stops the program from running and the error messages don't display
That's the point. If your update_attributes! fails, there's no reason to proceed with the rest of your code, as all that does is create/update new records. The point of the transactions is to roll those changes back anyways, so the exception is doing its job perfectly by preventing that code from running.
If your validation errors aren't displaying, you should handle the exception and render normally to prevent Rails from rendering an error page when the exception leaves your method.
I have a model that, when a record is inserted, needs to call a webservice.
If the webservice fails ( timeout or other failures ), them the save in database should also be reverted.
I used the after_save callback and tried to raise an ActiveRecord::Rollback when this kind of error happens.
Although it returns false on object.save, it doesn't rollback the transaction. What is the proper way of doing this?
How can i also make sure that the record won't be created?
Try to use before_save and return false from it.
Are you wrapping this in an Active Record Transaction block?
User.transaction do
User.create(:username => 'Kotori')
User.transaction(:requires_new => true) do
User.create(:username => 'Nemu')
raise ActiveRecord::Rollback
end
end
Also See:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html