对 jsonb 数组元素唯一性的 Rails 验证

Rails validation on jsonb array element uniqueness

提问人:Samuel-Zacharie Faure 提问时间:5/3/2023 更新时间:5/3/2023 访问量:125

问:

我有一个 Author 模型和一个 Book 模型。

一个作者可以有很多书,一本书也可以有很多作者。因此,它们通过第三个模型 AuthorBook 链接。

该模型有三个字段:author_id、book_id,还有:角色。

Roles 是一个 jsonB 数组,包含该作者相对于本书的角色。例如,它可以是“main_author”、“校正器”、“翻译器”等。作者可以有 1 个、多个或所有角色。

我想要一本 AuthorBook,对于一本给定的书,只允许一个作者担任特定角色。只能有一个“main_author”、“校正器”等。

我尝试实现如下验证:

class AuthorBook < ApplicationRecord
  validate :uniqueness_of_author_role_for_book

  def uniqueness_of_author_role_for_book
    others_records = AuthorBook.where(book_id:).where.not(user_id:)

    return if others_records.blank?

    other_records.each do |record|
      errors.add(:roles, "#{record.roles.join(',')} already exists on another user") if roles_already_assigned?(record)
    end
  end

  private

  def roles_already_assigned?(record)
    roles.map(&:to_s).intersect?(record.roles)
  end
end

可悲的是,在一种边缘情况下,这是行不通的。

如果我将 AuthorBook 的作者从 author1 更新为 author2,角色相同;这段代码将检测 author1 的存在,该 author1 具有与此完全相同角色的本书的 AuthorBook,并且验证将失败。

它不应该失败,因为在更新后,author1 将不再具有该角色,并且它应该有效。

可能有更好的方法可以做到这一点,但我似乎无法确定它。

Ruby-on-Rails Ruby PostgreSQL 验证 ActiveRecord

评论


答:

2赞 Arctodus 5/3/2023 #1

另一种方法是为每个角色使用一条记录,而不是数组列。然后,您可以简单地使用标准唯一性验证,作用域AuthorBookbook_id

class AuthorBook
  validates :role, uniqueness: { scope: :book_id }

评论

0赞 Samuel-Zacharie Faure 5/3/2023
这是一个有趣的命题。我需要将类的名称更改为不太传统的名称,例如“AuthorBookWithRole”(欢迎提出建议)以传达我们有一个独特角色的想法,但我想它可以工作。如果没有更好的建议,会将其标记为已接受的答案。
0赞 Samuel-Zacharie Faure 5/3/2023
经过一番思考,这可能是最好的方法。