2017-07-14 79 views
0

如果您在創建記錄時保存has_many:through關聯,如何確保關聯具有唯一對象。唯一性由一組自定義屬性定義。確保has_many:通過關聯在創建時是唯一的

考慮:

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和角色的最佳方式是獨特的基於關聯任意條件?

回答

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:參數是可選的,但可以很方便的情況下,字段名的串聯變得很長(並拋出一個錯誤)。

+0

謝謝。但是這對驗證錯誤很有用,但是,問題實際上是如何在驗證之前進行消毒,而不是簡單地將錯誤消除。如果有人放置相同的角色兩次,我*可能會拋出一個錯誤並讓他們修復它,或者我可以提供良好的用戶體驗併爲其重新進行刪除。 –

+0

如果你做了'valid?'之類的事情,你就不會拋出錯誤,並且可以根據錯誤的性質向用戶提供具體的反饋。這是一個非常標準的SOP。你可以在嘗試'valid''或'save'或'create'之前自己做一些手動檢查,但是你只是在複製ActiveRecord的功能,我不知道你爲什麼要這麼做。 – jvillian

1

要做到這一點,最好的方法是使用關係數據庫,特別是在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 

關係的DB設計,以保證引用完整性,所以用它正是這麼做的。任何ruby/rails-only解決方案都會有競爭條件和/或效率低下。

如果要提供用戶友好的消息,並檢查「以防萬一」,乾脆去檢查:

already_has_role = UserRole.exists?(user: user, role: prospective_role_additions) 

你仍然必須處理潛在的異常,當您嘗試堅持角色另外,但是。

+0

當然,在數據庫級別有最終決定權,但如果發生這種情況,應用程序會拋出異常。我仍然希望在發送到數據庫之前進行消毒,並避免常見情況下的異常流程。 –

+0

你的意思是像https://stackoverflow.com/a/8034383?無論如何,它需要對另一個表進行額外的查詢,而在1%的情況下,您仍然必須處理異常。爲什麼不編寫自己的更新方法來捕獲這個特殊的異常情況並進行適當的處​​理,從而覆蓋100%的情況? – Kache

+0

驗證在db級別沒有索引時不是100%有效。 IMO最佳答案。 – bkunzi01

相關問題