确保has_many:通过关联在创建

时间:2017-07-14 21:58:01

标签: ruby-on-rails activerecord associations

如果您在创建记录时通过关联保存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和角色的最佳方法是什么?

2 个答案:

答案 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:参数是可选的,但如果字段名称的连接太长(并抛出错误),则可以很方便。