关于如何通过has_many:through关系正确设置验证的指南?

时间:2019-02-08 09:03:41

标签: ruby-on-rails

我已经建立了三个模型:User,List和UserList,后者是has_many_through关系中User和List之间的联接模型。

我正在尝试设置我认为应该是非常原始的唯一性约束-但这并不奏效。请感谢您的指导/建议!

技术细节

我有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添加了标题为“黑暗骑士”的列表实际上不应该创建新的List记录-它应该创建一个新的/与众不同的UserList关联,指向先前创建的列表项。

(有些切线,但是我也在表上添加了唯一索引,因为我知道这避免了竞争情况)

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

这是哪里出了问题。

以亚当用户的身份登录,并将新标题“黑暗骑士”添加到我的列表中。

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

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

这将正确地导致创建新的列表记录和关联的用户列表记录。哇!

作为亚当,如果我尝试将相同的标题“黑暗骑士”再次添加到我的列表中,则什么都没有发生-包括控制台上没有错误。哇!

但是-作为Beatrice用户,如果我登录并现在尝试将“黑暗骑士”添加到我的列表中,则现在在控制台中出现错误:

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

我的调试和假设

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

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

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

然后作为Beatrice,似乎相同的控制器操作未找到与我的当前用户相关联的现有“黑暗骑士”列表项,因此它试图触发create动作。

但是,此创建操作尝试创建一个具有已经存在的标题的新List项,即,它违反了List.rb模型唯一性验证。

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

感觉好像我在这里错过了一些简单的事情。或者可能不是。非常感谢您提供有关如何进行操作的指导。谢谢!

1 个答案:

答案 0 :(得分:0)

我有99%的把握是current_user.lists.find_or_create_by只会搜索用户具有UserList条目的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