Ruby on Rails 验证绕过和混淆

Ruby on Rails validation bypass and confusion

提问人:john bowlee 提问时间:2/18/2023 更新时间:2/18/2023 访问量:121

问:

我在Ruby on Rails上遇到了一个奇怪的事情,我不知道处理这种情况的正确方法是什么。

我有两个模型,Book 和 Page。

书籍(name:string)

页码(page_number:整数,book_id:ID)

class Book < ActiveRecord::Base
  has_many :pages
  accepts_nested_attributes_for :pages
end

class Page < ActiveRecord::Base
  belongs_to :book
  validates_uniqueness_of :page_number, scope: :book_id
end

我创建了一个视图,从中可以更新一本书。我接受页面的嵌套属性,并且有一个部分我可以更新书籍页面以及添加新页面(通过 Javascript 函数,允许用户通过单击 + 按钮添加新的页面行)。正如你所看到的,我有一个验证,要求某本书的页码是唯一的,以防止重复。我还在数据库级别定义了一个唯一的索引(使用 Postgres)

在 Book 的控制器中的更新操作中,我有:

def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[:book])
    flash[:notice] = 'Book successfully modified!'
  end
end

我的方法的问题是,有时绕过了我在 Pages 模型上定义的page_number的验证,我直接从 PG 收到错误(“PG::UniqueViolation: ERROR: duplicate key value violates unique constraint”)。

这种情况发生在 2 种情况下:

  1. 尝试直接在一个表单提交上创建两个或多个具有相同编号的页面
  2. 更新现有page_number(例如从 page_number:4 到 nr:5),并在一次表单提交上创建一个数字为 4 的新页面。

似乎存在一些并发性和处理更新/创建的顺序问题。

对于第 2 点,我们应该以某种方式告诉 Rails 查看所有记录,看看我们是否试图通过将更新与创建相结合来进行任何重复。第 2 点是一个有效的选项,应该不会抛出验证错误,但是因为 Rails 首先进行创建,所以它遗憾地发现一个page_number 4 的页面已经存在(没有考虑到page_number 4 正在更新到 5)

我将不胜感激,您可以建议我如何处理这种情况并能够预测所有用例,以便在发生验证错误时我不会访问数据库。

如果这是不可能的,有没有办法从Postgres中捕获错误,格式化并将其显示给用户?

我感谢任何建议!

Ruby-on-Rails 验证 Ruby-on-Rails-3

评论

1赞 max 2/19/2023
具有应用程序级别唯一性验证的竞争条件是一个非常古老且已知的问题。这里发生的事情是,您向数据库发送两个查询,以查看是否存在具有该page_number的页面,并且由于它们都没有插入,因此它们都得到了 OK。 仅检查数据库中是否存在记录,而不是检查未保存的集合是否不包含重复项。validates_uniqueness_of
0赞 max 2/19/2023
我真的会推荐这篇文章 - thoughtbot.com/blog/the-perils-of-uniqueness-validations 它解释了这是如何发生的,那么即使它实际上不包含任何解决方案,我也可以。
0赞 max 2/19/2023
数据库驱动程序的异常可以像捕获任何其他异常一样被捕获 - 使用 .rescue PG::UniqueViolation
0赞 john bowlee 2/19/2023
@max感谢您的解释。在这种情况下,您建议做什么,只是从 PG::UniqueViolation 中拯救,或者我应该重构我的代码逻辑?我们还必须考虑我举例的场景 2。这是一个有效的示例,不应引发验证错误。
0赞 max 2/19/2023
挽救数据库 dirver 错误似乎是一个非常可怕的措施。我想您可以在 Book 中添加一个验证,您可以在其中检查内存中是否有任何页面记录包含重复的页码。这解决了部分问题。不完全确定在更新“释放插槽”时如何处理。

答: 暂无答案