9

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

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

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

def do_nothing

end

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

4

2 回答 2

16

一般来说,你的异常处理应该在最接近错误的地方,你可以对异常做一些明智的事情。在你的情况下,你会想要你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,PG::Error如果您直接与数据库交谈而不是通过 ActiveRecord,您会得到一个。
  2. 替换INDEX_NAME_GOES_HERE为唯一索引的真实名称。
  3. 您只想忽略您期望的特定约束违规,因此next if(...)后面是无参数的位raise(即,如果它不是您期望看到的,则重新引发异常)。
于 2013-04-02T05:38:37.877 回答
2

如果您在模型上放置 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 中没有唯一性约束。当然,您在 DB 中有一个独特的约束,这让 Rails 感到惊讶并导致它在最后一刻崩溃。

如果性能很关键,那么可能会忽略此建议。在 Rails 模型上具有唯一性约束会导致它SELECT在每个之前执行一次INSERT,以便在 Rails 级别进行唯一性验证,这可能会使循环正在执行的查询数量增加一倍。像您正在做的那样在数据库级别捕获错误可能是一种合理的优雅换取性能的交易。

(编辑)TL;DR:在数据库中始终具有唯一约束。还具有模型约束将允许在 DB 引发错误之前进行 ActiveRecord/ActiveModel 验证。

于 2013-04-02T13:22:36.043 回答