【问题标题】:Rails 3 ignore Postgres unique constraint exceptionRails 3 忽略 Postgres 唯一约束异常
【发布时间】:2013-04-02 05:07:52
【问题描述】:

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

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

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

def do_nothing

end

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

【问题讨论】:

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


    【解决方案1】:

    一般来说,你的异常处理应该在最接近错误的地方,你可以对异常做一些明智的事情。在你的情况下,你希望你的 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(即,如果它不是您期望看到的,则重新引发异常)。

    【讨论】:

    • 谢谢,穆。看起来这样奏效了。我曾尝试在 do_nothing 方法中嵌入“下一个”,称为rescue_with,但这给了我一个错误。无论如何,这绝对是一个 PG::Error,而且我没有直接与数据库交谈。它只是一个普通的旧 habtm 连接表,没有自定义 SQL 插入。也许 ActiveRecord::RecordNotUnique 只在模型表上被调用?
    • 我说得太早了。再次测试它,它肯定仍然在它遇到的第一个副本上终止循环。有趣的是,如果我在控制器方法中插入 begin/rescue 块并从控制器中删除rescue_with,那么根本不会捕获到异常。因此,出于某种原因,这显然根本没有在控制器方法级别捕获。
    • 你不想在控制器级别捕获它,这离你可以对异常做些什么的地方太远了。你的代码是什么样的?
    • 好的,终于搞定了。将 PG::Error 更改为 ActiveRecord::RecordNotUnique (如您最初建议的那样)并修复了它。不过,这很奇怪。控制台日志仅引用 PG::Error。没有提到 ActiveRecord 异常。
    【解决方案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 模型上具有唯一性约束会导致它在每个 INSERT 之前执行一个 SELECT 以便它在 Rails 级别进行唯一性验证,这可能会使您的循环执行的查询数量增加一倍。像您一样在数据库级别捕获错误可能是一种合理的优雅换取性能的交易。

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

    【讨论】:

    • Rails 唯一性验证应该始终由数据库内的唯一性约束支持。所有 ActiveRecord 验证都受到竞争条件的影响,因此逻辑必须在数据库中,如果您不想要损坏的数据,则必须处理异常。
    • 你说的很对,我不是故意的。当谈到模型验证时,我绝对指的是除了数据库中的唯一约束之外。您需要数据库中的约束来保证完整性,并且需要 Rails 中的约束来进行 ActiveRecord/ActiveModel 验证。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-06
    • 1970-01-01
    • 2020-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多