Valider l'unicité de plusieurs colonnes

193

Existe-t-il un moyen de 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 comme:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
re5et
la source
7
pardonne-moi si je suis dense, mais comment cela aiderait-il dans cette situation?
re5et le
2
essayez d'utiliser "validates_uniqueness_of" dans votre modèle. si cela ne fonctionne pas, essayez de créer un index sur lequel vous pouvez créer une migration de feilds qui inclut une instruction comme add_index: table, [: column_a,: column_b],: unique => true)
Harry Joy
1
@HarryJoy, a-t-il demandé Is there a rails-way way. Et vous lui proposez de manière non-rails, mais standard. The Active Record way claims that intelligence belongs in your models, not in the database.
Vert
2
Malheureusement, elle validates :field_name, unique: trueest sujette aux conditions de course, donc même si contre rail-way, une contrainte réelle est préférée. @HarryJoy Je vais voter pour une réponse décrivant la manière de contrainte.
Pooyan Khosravi
1
meilleure réponse que tout ce qui est noté ci-dessous est celui-ci stackoverflow.com/a/34425284/1612469 car il apporte une autre couche pour s'assurer que tout fonctionnera correctement
Aleks

Réponses:

319

Vous pouvez définir un validates_uniqueness_ofappel comme suit.

validates_uniqueness_of :user_id, :scope => :friend_id
Dylan Markow
la source
83
Je voulais juste ajouter que vous pouvez passer plusieurs paramètres de portée au cas où vous auriez besoin de valider l'unicité sur plus de 2 champs. Ie: scope => [: friend_id,: group_id]
Dave Rapin
27
Bizarre que tu ne puisses pas dire validates_uniqueness_of [:user_id, :friend_id]. Peut-être que cela doit être corrigé?
Alexey
12
Alexey, validates_uniqueness_of [: user_id,: friend_id] fera juste la validation pour chacun des champs listés - et c'est le comportement documenté et attendu
Nikita Hismatov
71
Dans Rails 4, cela devient: valide: user_id, unicité: {scope:: friend_id}
Marina Martin
3
Vous souhaitez probablement ajouter un message d'erreur personnalisé comme,: message => 'a déjà cet ami.'
laffuste
137

Vous pouvez utiliser validatespour valider uniquenesssur une colonne:

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

La syntaxe de 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 et ne peuvent pas garantir la cohérence. Prenons l'exemple suivant:

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

  2. plusieurs ( deux ou plus ) demandes simultanées, traitées par des processus séparés chacun ( serveurs d'applications, serveurs de travail en arrière-plan ou tout ce que vous utilisez ), accédez à la base de données pour 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 n champs;

  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 une contrainte unique à la table db. Vous pouvez le définir avec une add_indexassistance 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

Attention : même après avoir défini une contrainte unique, deux requêtes simultanées ou plus essaieront d'écrire les mêmes données dans db, mais au lieu de créer des enregistrements en double, cela déclenchera une ActiveRecord::RecordNotUniqueexception, que vous devrez gérer séparément:

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