web-dev-qa-db-fra.com

Valider l'unicité de plusieurs colonnes

Existe-t-il un moyen Rails-way pour valider qu'un enregistrement réel est unique et pas seulement une colonne? Par exemple, un modèle/une table d’amitié ne devrait pas pouvoir avoir plusieurs enregistrements identiques, tels que:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
186
re5et

Vous pouvez viser un validates_uniqueness_of appelez comme suit.

validates_uniqueness_of :user_id, :scope => :friend_id
316
Dylan Markow

Vous pouvez utiliser validates pour valider uniqueness sur une colonne:

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

La syntaxe pour la validation sur plusieurs colonnes est similaire, mais vous devez plutôt fournir un tableau de champs:

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

Cependant, les approches de validation présentées ci-dessus ont une condition de concurrence critique et ne peuvent pas garantir la cohérence. Prenons l'exemple suivant:

  1. les enregistrements de table de base de données sont supposés être uniques par les champs n;

  2. plusieurs demandes simultanées ( deux ou plus), gérées par des processus distincts ( serveurs d'applications, serveurs de travailleurs d'arrière-plan ou tout ce que vous utilisez), base de données d'accès à insérer le même enregistrement dans la table;

  3. chaque processus en parallèle valide s'il existe un enregistrement avec les mêmes champs n;

  4. la validation de chaque demande est réussie et chaque processus crée un enregistrement dans la table avec les mêmes données.

Pour éviter ce genre de comportement, il faut ajouter un contrainte unique à la table db. Vous pouvez le configurer avec add_index aide pour un (ou plusieurs) champ (s) en exécutant la migration suivante:

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

Caveat: même après avoir défini une contrainte unique, deux demandes simultanées ou plus essaieront d'écrire les mêmes données dans la base de données, mais au lieu de créer des enregistrements en double, cela déclenche un ActiveRecord::RecordNotUnique exception, que vous devez gérer séparément:

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

Vous avez probablement besoin de contraintes réelles sur la base de données, car validates souffre de conditions de concurrence.

validates_uniqueness_of :user_id, :scope => :friend_id

Lorsque vous conservez une instance d'utilisateur, Rails validera votre modèle en exécutant une requête SELECT pour voir si des enregistrements d'utilisateur existent déjà avec l'ID utilisateur fourni. En supposant que l'enregistrement se révèle valide, Rails exécutera l’instruction INSERT pour conserver l’utilisateur. Cela fonctionne très bien si vous exécutez une seule instance d’un seul serveur Web de processus/thread.

Si deux processus/threads tentent de créer un utilisateur avec le même id_utilisateur à peu près au même moment, la situation suivante peut se produire. Race condition with validates

Avec des index uniques sur la base de données en place, la situation ci-dessus se présentera comme suit. Unique indexes on db

Réponse tirée de cet article de blog - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations

34
Deepak Azad

Cela peut être fait avec une contrainte de base de données sur les deux colonnes:

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

Vous pouvez utiliser un validateur Rails), mais en général, je recommande d'utiliser une contrainte de base de données.

More reading: https://robots.thoughtbot.com/validation-database-constraint-or-both

2
Tate Thurston