有关如何使用has_many:through关系正确设置验证的指导?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有关如何使用has_many:through关系正确设置验证的指导?相关的知识,希望对你有一定的参考价值。

我设置了三个模型:User,List和UserList - 后者是用户和List之间的连接模型,采用has_many_through关系。

我正在尝试设置我认为应该是相当普遍的唯一性约束 - 但它并不是很有效。非常感谢您的指导/建议!

技术细节

我有3个型号:

class User < ApplicationRecord
  has_many :user_lists
  has_many :lists, through: :user_lists, dependent: :destroy
End

class List < ApplicationRecord
  has_many :user_lists
  has_many :users, through: :user_lists, dependent: :destroy

  # no duplicate titles in the List table
  validates :title, uniqueness: true
End

class UserList < ApplicationRecord
  belongs_to :list
  belongs_to :user

  # a given user can only have one copy of a list item
  validates :list_id, uniqueness: { scope: :user_id }
end

正如您所看到的,我希望列表项基于其标题是唯一的。换句话说,如果用户Adam添加了标题为“黑暗骑士”的列表,那么用户Beatrice添加标题为“黑暗骑士”的列表实际上不应该创建新的列表记录 - 它应该只创建一个新的/不同的UserList关联,指向先前创建的List项。

(有点切向,但我也在桌子上添加了一个独特的索引,因为我明白这可以避免竞争条件)

class AddIndexToUserLists < ActiveRecord::Migration[5.2]
  def change
    add_index :user_lists, [:user_id, :list_id], unique: true
  end
end

这里出了问题。

作为用户Adam,我登录,并将新标题“The Dark Knight”添加到我的列表中。

这是控制器动作(假设current_user正确检索Adam):

# POST /lists
    def create
      @list = current_user.lists.find_or_create_by!(list_params)
    end

这正确地导致创建新的List记录和关联的UserList记录。欢呼!

作为亚当,如果我尝试将同样的标题“黑暗骑士”添加到我的列表中,则没有任何反应 - 包括控制台上没有错误。欢呼!

然而 - 作为用户Beatrice,如果我登录并且现在尝试将“The Dark Knight”添加到我的列表中,我现在在控制台中出现错误:

POST http://localhost:3000/api/v1/lists 422 (Unprocessable Entity)

我的调试和假设

如果我删除List.title上的唯一性约束,则此错误消失,并且Beatrice能够将“黑暗骑士”添加到她的列表中。

然而,List然后包含两个标题为“黑暗骑士”的记录,这似乎是多余的。

作为亚当,似乎current_user.lists.find_or_create_by!(list_params)在我的控制器动作中找到了与我当前用户相关的现有“黑暗骑士”列表,并意识到它存在 - 从而不会触发创建动作。

然后作为比阿特丽斯,似乎相同的控制器动作没有找到与我当前用户相关联的现有“黑暗骑士”列表项 - 因此它尝试触发创建动作。

但是,此创建操作尝试创建一个新的List项,其标题已存在 - 即它与List.rb模型唯一性验证相违背。

我不确定如何修改find​​_or_create_by操作或模型验证,以确保为Beatrice创建新的UserList记录/关联 - 但不是新的List记录(因为已经存在)。

感觉就像我在这里错过了一些简单的东西。或者可能不是。非常感谢有关如何进行的一些指导。谢谢!

答案

我99%肯定发生的事情是current_user.lists.find_or_create_by只会搜索用户有UserList条目的List记录。因此,如果List存在但当前用户没有关联,它将尝试创建一个与现有列表冲突的新列表。

假设这是问题,您需要独立于用户关联找到List:@list = List.find_or_create_by(list_params)

获得该列表后,可以通过关联或UserList模型创建UserList记录。如果您正在寻找简洁,我认为您可以使用current_user.lists << @list来创建UserList,但是如果用户已经拥有该列表的UserList,您应该检查它的行为,我不确定它是否会覆盖您现有的数据。

所以(假设<<方法适用于创建UserList),您的控制器操作可能如下所示:

def create
    @list = List.find_or_create_by!(list_params)
    current_user.lists << @list
end

以上是关于有关如何使用has_many:through关系正确设置验证的指导?的主要内容,如果未能解决你的问题,请参考以下文章

何时在 Rails 中使用“has_many :through”关系?

如何避免 has_many :through 关系中的重复?

设置多态 has_many :through 关系

使用 has_many :through 和 build

如何实现has_many:通过与Mongoid和mongodb的关系?

ActiveRecord、has_many :through 和多态关联