178

実際のレコードが単なる列ではなく、一意であることを検証するための方法はありますか?あなたはスコープvalidates_uniqueness_of呼び出しは次のようにすることができます複数の列の一意性を確認する

user_id: 10 | friend_id: 20 
user_id: 10 | friend_id: 20 
+7

もし私が密集しているなら、私に許してください。しかし、この状況でどうすれば助けになるのですか? – re5et

+2

あなたのモデルで "validates_uniqueness_of"を試してみてください。これは、 add_index:table、[:column_a、:column_b]、:unique => trueのような文を含むフィールドのマイグレーションを作成できるインデックスを作成しようとするとうまくいきません。 –

+1

@HarryJoy、レールの道がありますか?そして、あなたは彼にノンレールの方法を提供しますが、標準です。 – Green

答えて

306

:たとえば、友情モデル/テーブルは以下のように複数の同一のレコードを持ってすることはできないはず。

validates_uniqueness_of :user_id, :scope => :friend_id 
+78

ちょうど複数のスコープパラメータを渡すことができます3つ以上のフィールドで一意性を検証する必要があります。私。 :scope => [:friend_id、:group_id] –

+25

あなたは 'validates_uniqueness_of [:user_id、:friend_id]'と言うことはできません。おそらく、これにパッチを当てる必要がありますか? – Alexey

+11

Alexey、validates_uniqueness_of [:user_id、:friend_id]はリストされた各フィールドの検証を行うだけで、文書化された動作が期待されます –

32

有効性が競合条件に苦しんでいるため、おそらくdbに実際の制約が必要です。

validates_uniqueness_of :user_id, :scope => :friend_id 

あなたはユーザーインスタンスを保持

は、Railsは任意のユーザレコードが既に提供のuser_idで存在するかどうかを確認するためにSELECTクエリを実行して、モデルを検証します。レコードが有効であると判断された場合、RailsはINSERT文を実行してユーザを永続させます。これは、単一のプロセス/スレッドWebサーバーの単一のインスタンスを実行している場合に効果的です。

2つのプロセス/スレッドが同じuser_idを持つユーザーを同じ時刻に作成しようとしている場合は、次のような状況が発生する可能性があります。 Race condition with validates

dbの固有のインデックスを使用すると、上記のような状況が発生します。このブログ記事から取ら Unique indexes on db

回答 - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations

+3

そして、Railsの移行システムを使用して、DB側の制約(複数の列間での固有の制約)をどのように達成できますか? –

+0

http://stackoverflow.com/questions/4123610/how-to-implement-a-unique-index-on-two-columns-in-rails –

+0

@LeeHanKyeolのようにすることができます def change add_index :users、:email、unique:true end end –

113

あなたは1列にuniquenessを検証するためにvalidatesを使用することができます。

validates :user_id, uniqueness: {scope: :friend_id} 

複数の列の検証のための構文は似ていますが、あなたがすべき代わりにフィールドの配列を指定します。

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]} 

ただし、の場合、上記の検証アプローチは競合状態にあり、一貫性を保証することはできません。次の例を考えてみましょう:

  1. データベーステーブルのレコードはN分野でユニークなことになっています。テーブル内の同じレコードを挿入するために別々のプロセスごと(アプリケーションサーバ、バックグラウンドワーカーサーバーまたはあなたがを使用しているものは何でも)、Accessデータベースで扱う

  2. 複数(2以上)同時要求、。

  3. 各プロセスは、同一のレコードがあるかどうかを検証します。nフィールド。

  4. 各リクエストの検証は正常に行われ、各プロセスは同じデータを持つテーブルにレコードを作成します。このような動作を回避するために

、1は、DBテーブルにユニーク制約を追加する必要があります。

class AddUniqueConstraints < ActiveRecord::Migration 
    def change 
    add_index :table_name, [:field1, ... , :fieldn], unique: true 
    end 
end 

警告:あなたは、次の移行を実行することにより、1つ(または複数)のフィールド(複数可)のためadd_indexヘルパーでそれを設定することができますが、ユニーク制約を設定した後でも、二つ以上の同時要求はなりますDBへ同じデータを書き込むために、代わりに重複するレコードを作成する、これはあなたが個別に処理する必要がありActiveRecord::RecordNotUnique例外を発生させます試してみてください。

begin 
# writing to database 
rescue ActiveRecord::RecordNotUnique => e 
# handling the case when record already exists 
end 
+1

ありがとうございます! – nuc

0

DB制約:

add_index :friendships, [:user_id, :friend_id], unique: true

関連する問題