実際のレコードが列だけでなく一意であることを検証するRailsの方法はありますか?たとえば、フレンドシップモデル/テーブルには、次のような複数の同一のレコードを含めることはできません。
user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
次のようにvalidates_uniqueness_of
呼び出しをスコープできます。
validates_uniqueness_of :user_id, :scope => :friend_id
validates
を使用して、1つの列でuniqueness
を検証できます。
validates :user_id, uniqueness: {scope: :friend_id}
複数列の検証の構文は似ていますが、代わりにフィールドの配列を提供する必要があります。
validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}
ただし、、上記の検証アプローチには競合状態があり、一貫性を保証できません。次の例を考えてみましょう。
データベーステーブルレコードは、nフィールドによって一意であると想定されています。
複数(2つ以上)の同時要求、個別のプロセスによって処理されます各(アプリケーションサーバー、バックグラウンドワーカーサーバー、または使用しているもの) )、データベースにアクセスして同じレコードをテーブルに挿入します。
同じnフィールドを持つレコードが存在する場合、並行して各プロセスが検証します。
各リクエストの検証は正常に渡され、各プロセスは同じデータでテーブルにレコードを作成します。
このような動作を回避するには、一意の制約をdbテーブルに追加する必要があります。次の移行を実行することで、1つ(または複数の)フィールドの add_index
ヘルパーで設定できます。
class AddUniqueConstraints < ActiveRecord::Migration
def change
add_index :table_name, [:field1, ... , :fieldn], unique: true
end
end
Caveat:一意の制約を設定した後でも、2つ以上の同時要求が同じデータをdbに書き込もうとしますが、重複レコードを作成する代わりに、 ActiveRecord::RecordNotUnique
例外。個別に処理する必要があります。
begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end
検証には競合状態が発生するため、おそらくデータベースに実際の制約が必要になるでしょう。
validates_uniqueness_of :user_id, :scope => :friend_id
ユーザーインスタンスを永続化すると、RailsはSELECTクエリを実行してモデルを検証し、指定されたuser_idを持つユーザーレコードが既に存在するかどうかを確認します。レコードが有効であると判明した場合、RailsはINSERTステートメントを実行してユーザーを永続化します。これは、単一のプロセス/スレッドWebサーバーの単一のインスタンスを実行している場合に効果的です。
2つのプロセス/スレッドが同じ時間に同じuser_idを持つユーザーを作成しようとすると、次の状況が発生する可能性があります。
DBに一意のインデックスを配置すると、上記の状況は次のようになります。
このブログ投稿からの回答- http://robots.thoughtbot.com/the-perils-of-uniqueness-validations
これは、2つの列のデータベース制約を使用して実行できます。
add_index :friendships, [:user_id, :friend_id], unique: true
Railsバリデーターを使用できますが、一般的にはデータベース制約の使用をお勧めします。
もっと読む: https://robots.thoughtbot.com/validation-database-constraint-or-both