2017-07-14 4 views
0

レコード作成時にhas_many:throughアソシエーションを保存する場合、アソシエーションに一意のオブジェクトがあることをどのように確認できますか。一意性は、カスタム属性セットによって定義されます。作成時にhas_many:throughアソシエーションが一意であることを確認してください

考慮:

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

ありがとうございました。しかし、これは検証エラーには良いですが、実際に検証する前にどのようにサニタイズするのかというだけでなく、単にエラーを出力するのではないでしょうか。誰かが同じ役割を2回実行すると、エラーを投げて修正することができます。あるいは、良いユーザーエクスペリエンスを提供して重複排除できます。 –

+0

'valid? 'のようなことをすれば、エラーは発生せず、エラーの性質に基づいてユーザーに特定のフィードバックを提供することができます。それはかなり標準的なSOPです。 'valid?'や 'save'や' create'など何かをしようとする前に手作業によるチェックを自分で行うことができますが、ActiveRecordの機能を複製するだけなので、なぜそうするのか分かりません。 – jvillian

1

これを行う最も良い方法は、特にリレーショナルデータベースを使用している場合は、user_rolesに一意の複数列インデックスを作成することです。

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には、参照整合性を保証するために設計されていますので、まさにそのためにそれを使用します。

add_index :user_roles, [:user_id, :role_id], unique: true

そして優雅な役割の追加が失敗したときに処理します。 Ruby/Rails専用のソリューションには競合状態があり、実際には効率が悪いです。

あなたはユーザーフレンドリーなメッセージングを提供し、「念のために」チェック、ちょうど先に行くとチェックしたい場合:

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

あなたはまだあなたが役割を持続しようとすると、潜在的な例外を処理する必要がありますしかし、追加。

+0

確かに、最終的にdbレベルで最終的な発言をするのは良いことですが、これが発生した場合、アプリケーションは例外をスローします。私はまだデータベースに送信する前に消毒して、一般的なケースの例外フローを回避したいと思います。 –

+0

https://stackoverflow.com/a/8034383のような意味ですか?とにかくもう一方のテーブルへの余分なクエリが必要で、1%の場合は例外を処理する必要があります。この特定の例外のケースをキャッチし、それを正常に処理する独自の更新メソッドを作成して、100%のケースをカバーするのはなぜですか? – Kache

+0

dbレベルでのインデックスなしでは、検証は100%有効ではありません。 IMOベストアンサー。 – bkunzi01

関連する問題