Rails 3 Ignores Postgres Exception Exception

What is the right way to save the exception and just continue processing? I have an application in which there are folders and items with a habtm relationship through a connection table called folder_items. This table has a unique constraint to ensure that there are no duplicate item / folder combinations. If a user tries to add an item to the same folder several times, I obviously do not want to add additional lines; but I do not want to stop processing.

Postgres automatically throws an exception when a unique constraint is violated, so I tried to ignore it in the controller as follows:

rescue PG::Error, :with => :do_nothing def do_nothing end 

This is great for single inserts. The controller renders with a status code of 200. However, I have another method that does voluminous inserts in a loop. In this method, the controller exits the loop when it encounters the first repeated line, which I don't want. At first I thought that the loop should be wrapped in a transaction that will be rolled back, but this is not so - all rows before the duplicate will be inserted. I want him to simply ignore the restriction exception and move on to the next element. How to prevent PG :: Error exception being thrown?

+7
source share
2 answers

In general, your exception handling should be in the immediate vicinity of the error with which you can do something reasonable with the exception. In your case, you want your rescue inside your loop, for example:

 stuff.each do |h| begin Model.create(h) rescue ActiveRecord::RecordNotUnique => e next if(e.message =~ /unique.*constraint.*INDEX_NAME_GOES_HERE/) raise end end 

A couple of interesting points:

  • Breaking the constraint inside the database will give you an ActiveRecord::RecordNotUnique , not the base PG::Error . AFAIK, you would get PG::Error if you spoke directly to the database, and not through ActiveRecord.
  • Replace INDEX_NAME_GOES_HERE with the real name of the unique index.
  • You just want to ignore the specific restriction restriction that you expect, therefore, the next if(...) bit, followed by the contactless raise (i.e., raise the exception again if that is not what you expect to see).
+12
source

If you put the Rails model on the model, then you can control the flow without causing an exception.

 class FolderItems belongs_to :item belongs_to :folder validates_uniqueness_of :item, scope: [:folder], on: :create end 

Then you can use

 FolderItem.create(folder: folder, item: item) 

It will return true if the association was created, false if an error occurs. This exception will not be. Using FolderItem.create! will throw an exception if the association is not created.

The reason you see PG errors is because Rails itself believes that the model is valid when saved, because the model class does not have a uniqueness constraint in Rails. Of course, you have a unique database constraint that surprises Rails and makes it explode at the last minute.

If performance is critical, perhaps ignore this tip. Having a uniqueness constraint for the Rails model forces it to perform a SELECT before each INSERT so that it performs a uniqueness check at the Rails level, potentially doubling the number of queries your loop executes. Just catching bugs at the database level, as you do, can be a reasonable trade of elegance for performance.

(change) TL; DR: Always have a unique constraint in the database. Also, the presence of a model constraint will allow checking ActiveRecord / ActiveModel before the DB generates an error.

+1
source

All Articles