如果您在创建记录时通过关联保存has_many:,如何确保关联具有唯一对象。 Unique由一组自定义属性定义。
考虑到:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, through: :user_roles
before_validation :ensure_unique_roles
private
def ensure_unique_roles
# I thought the following would work:
self.roles = self.roles.to_a.uniq{|r| "#{r.project_id}-#{r.role_id}" }
# but the above results in duplicate, and is also kind of wonky because it goes through ActiveRecord assignment operator for an association (which is likely the cause of it not working correctly)
# I tried also:
self.user_roles = []
self.roles = self.roles.to_a.uniq{|r| "#{r.project_id}-#{r.role_id}" }
# but this is also wonky because it clears out the user roles which may have auxiliary data associated with them
end
end
根据关联的任意条件验证user_roles和角色的最佳方法是什么?
答案 0 :(得分:3)
执行此操作的最佳方法,尤其是在使用关系数据库时,是在user_roles
上创建唯一的多列索引。
add_index :user_roles, [:user_id, :role_id], unique: true
然后在角色添加失败时优雅地处理:
class User < ActiveRecord::Base
def try_add_unique_role(role)
self.roles << role
rescue WhateverYourDbUniqueIndexExceptionIs
# handle gracefully somehow
# (return false, raise your own application exception, etc, etc)
end
end
关系数据库旨在保证参照完整性,因此请将其用于此目的。任何ruby / rails-only解决方案都会有竞争条件和/或效率非常低。
如果您想提供用户友好的消息传递并检查“以防万一”,请继续检查:
already_has_role = UserRole.exists?(user: user, role: prospective_role_additions)
但是,当您尝试保留角色添加时,仍然需要处理潜在的异常。
答案 1 :(得分:1)
只需进行多字段验证。类似的东西:
class UserRole < ActiveRecord::Base
validates :user_id,
:role_id,
:project_id,
presence: true
validates :user_id, uniqueness: { scope: [:project_id, :role_id] }
belongs_to :user, :project, :role
end
这样的事情将确保用户只能为给定项目拥有一个角色 - 如果这是您正在寻找的内容。
正如Kache所提到的,你可能也想要做一个db级索引。整个迁移可能类似于:
class AddIndexToUserRole < ActiveRecord::Migration
def change
add_index :user_roles, [:user_id, :role_id, :project_id], unique: true, name: :index_unique_field_combination
end
end
name:
参数是可选的,但如果字段名称的连接太长(并抛出错误),则可以很方便。