17

我有两个具有 HABTM 关系的模型——用户和角色。

  • 用户 - has_and_belongs_to_many :roles
  • 角色 - belongs_to :user

我想在连接(users_roles 表)中添加一个唯一性约束,说明 user_id 和 role_id 必须是唯一的。在 Rails 中,看起来像:

validates_uniqueness_of :user, :scope => [:role]

当然,在 Rails 中,我们通常没有模型来表示 HABTM 关联中的连接关系。

所以我的问题是添加约束的最佳位置在哪里?

4

4 回答 4

38

您可以为连接表添加唯一性

add_index :users_roles, [ :user_id, :role_id ], :unique => true, :name => 'by_user_and_role'

请参阅在连接表中,Rails 缺少复合键的最佳解决方法是什么?

然后,您的数据库将引发异常,您必须处理该异常。
我不知道任何准备好在这种情况下使用 rails 验证,但是您可以像这样添加自己的验证:

class User < ActiveRecord::Base
has_and_belongs_to_many :roles, :before_add => :validates_role

我会默默地放弃数据库调用并报告成功。

def validates_role(role)
  raise ActiveRecord::Rollback if self.roles.include? role
end

ActiveRecord::Rollback 在内部被捕获但不被重新引发。

编辑

不要使用我添加自定义验证的部分。它有点工作,但有更好的选择。

使用:uniq@Spyros 在另一个答案中建议的关联选项:

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies, :uniq => true, :read_only => true
end  

(此代码片段来自 Rails Guides v.3)。阅读Rails Guides v 3.2.13寻找 4.4.2.19 :uniq

Rails Guide v.4 特别警告不要使用include?for 检查唯一性,因为可能存在竞争条件。

关于添加索引以连接表的部分仍然存在。

于 2011-02-14T13:22:11.147 回答
11

在 Rails 5 中,您需要使用distinct而不是uniq

另外,试试这个以确保唯一性

has_and_belongs_to_many :foos, -> { distinct } do
  def << (value)
    super value rescue ActiveRecord::RecordNotUnique
  end
end
于 2016-09-22T00:29:49.313 回答
5

我认为使用 :uniq => true 可以确保您没有重复的对象。但是,如果您想在将第二个副本写入数据库之前检查是否存在重复项,我可能会使用 find_or_create_by_name_and_description(...)。

(当然名称和描述是您的列值)

于 2011-02-14T03:57:11.847 回答
5

我更喜欢

class User < ActiveRecord::Base
  has_and_belongs_to_many :roles, -> { uniq }
end

其他选项参考这里

于 2014-05-26T05:11:31.240 回答