Rails 3忽略Postgres唯一约束异常

时间:2013-04-02 05:07:52

标签: ruby-on-rails ruby-on-rails-3 postgresql activerecord rails-activerecord

救援异常并继续处理的正确方法是什么?我有一个具有文件夹和项目的应用程序,通过名为folders_items的连接表具有habtm关系。该表具有唯一约束,确保没有重复的项目/文件夹组合。如果用户尝试多次将项目添加到同一文件夹,我显然不希望添加其他行;但我也不想停止处理。

当违反唯一约束时,Postgres会自动抛出异常,所以我试着在控制器中忽略它,如下所示:

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

def do_nothing

end

这适用于单次插入。控制器执行渲染,状态代码为200.但是,我有另一种方法在循环中执行批量插入。在该方法中,控制器在遇到第一个重复行时退出循环,这不是我想要的。起初,我认为循环必须包含在正在回滚的事务中,但事实并非如此 - 重复插入之前的所有行。我希望它只是忽略约束异常并移动到下一个项目。如何防止PG :: Error异常中断此操作?

2 个答案:

答案 0 :(得分:13)

通常,您的异常处理应位于错误的最近点,您可以使用异常做一些合理的事情。在您的情况下,您需要在循环中使用rescue,例如:

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

有几点兴趣:

  1. 数据库中的约束违规将导致ActiveRecord::RecordNotUnique错误,而不是基础PG::Error。 AFAIK,如果您直接与数据库交谈而不是通过ActiveRecord,您将获得PG::Error
  2. INDEX_NAME_GOES_HERE替换为唯一索引的真实姓名。
  3. 您只想忽略您所期望的特定约束违规,因此next if(...)位后跟无争论raise(即如果它不是您的话,则重新引发异常)期待看到。)

答案 1 :(得分:1)

如果您在模型上放置了Rails验证器,那么您可以控制流量而不会抛出异常。

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

然后你可以使用

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

如果创建了关联,它将返回true,如果存在错误,则返回false。它不会抛出异常。如果未创建关联,则使用FolderItem.create!会抛出异常。

您看到PG错误的原因是因为Rails本身认为模型在保存时有效,因为模型类在Rails中没有唯一性约束。当然,你在数据库中有一个独特的约束,这会让Rails感到惊讶并导致它在最后一分钟爆炸。

如果表现很关键,那么可能会忽略这个建议。在Rails模型上具有唯一性约束会导致它在每SELECT之前执行INSERT,以便在Rails级别执行唯一性验证,从而可能使循环执行的查询数量翻倍。只是像你一样在数据库级别捕获错误可能是一种合理的性能交易。

(编辑)TL; DR:始终在DB中具有唯一约束。同样具有模型约束将允许在DB抛出错误之前进行ActiveRecord / ActiveModel验证。