Rails 3忽略Postgres的唯一性约束异常

9
什么是正确的处理异常并简单地继续处理的方法?我的应用程序具有文件夹和项目,通过名为folders_items的联接表进行habtm关系。该表具有唯一约束条件,确保没有重复的item / folder组合。如果用户尝试将项目添加到相同的文件夹中多次,则显然不希望添加其他行;但我也不想停止处理。
当违反唯一约束条件时,Postgres会自动抛出异常,因此在控制器中,我尝试忽略它,如下所示:
rescue PG::Error, :with => :do_nothing

def do_nothing

end

这在单个插入时可以正常工作。控制器执行带有状态码200的渲染。但是,我有另一种方法,在循环中进行批量插入。在该方法中,当遇到第一个重复行时,控制器退出循环,这不是我想要的结果。起初,我认为循环必须被包装在一个事务中,而该事务被回滚了,但实际上并不是这样 - 所有在重复之前的行都被插入了。我希望它可以简单地忽略约束异常并移动到下一个项目。如何防止PG :: Error异常中断此过程?

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。据我所知,如果您直接与数据库通信而不是通过 ActiveRecord,则会得到 PG::Error
  2. INDEX_NAME_GOES_HERE 替换为唯一索引的实际名称。
  3. 您只想忽略特定的约束违规,因此需要使用 next if(...) 位后面跟着无参数的 raise(即如果不是您期望看到的异常,则重新引发异常)。

谢谢,mu。看起来这很有效。我曾尝试将“next”嵌入到do_nothing方法中以被rescue_with调用,但那样会出错。不管怎样,它绝对是一个PG::Error错误,并且我没有直接与db交流。它只是一个普通的habtm联接表,没有自定义SQL插入。也许ActiveRecord::RecordNotUnique只在模型表上调用? - J Plato
我说得太早了。再次测试后,它肯定仍然会在遇到第一个重复项时终止循环。有趣的是,如果我在控制器方法中插入begin/rescue块并从控制器中删除rescue_with,则根本不会捕获异常。因此,由于某种原因,这显然根本没有在控制器方法级别上被捕获。 - J Plato
你不想在控制器层面捕获它,那太远了,你无法对异常做出处理。你的代码是什么样子的? - mu is too short
好的,终于搞定了。将 PG::Error 更改为 ActiveRecord::RecordNotUnique(正如您最初建议的那样),问题得到了解决。不过很奇怪,控制台日志只提到了 PG::Error,没有提到 ActiveRecord 异常。 - J Plato

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,以便在Rails级别进行唯一性验证,可能会使您的循环执行的查询数量翻倍。像您现在所做的那样,在数据库级别捕获错误可能是一种合理的折衷方案。总之:始终在数据库中具有唯一性约束条件。此外,在DB引发错误之前,使用模型约束将允许ActiveRecord/ActiveModel进行验证。

5
Rails中的唯一性验证应始终由数据库内的唯一性约束支持。所有ActiveRecord验证都受到竞争条件的影响,因此逻辑必须在数据库中处理,否则您将不得不处理异常以避免损坏的数据。 - mu is too short
你说得完全正确,我并不是想暗示其他意思。当谈到模型验证时,我肯定是指除了数据库中的唯一约束之外的内容。你需要在数据库中使用约束来保证数据完整性,在Rails中使用ActiveRecord/ActiveModel验证来进行约束。 - Chris Aitchison

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接