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
Vous pouvez viser un validates_uniqueness_of
appelez comme suit.
validates_uniqueness_of :user_id, :scope => :friend_id
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:
les enregistrements de table de base de données sont supposés être uniques par les champs n;
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;
chaque processus en parallèle valide s'il existe un enregistrement avec les mêmes champs n;
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
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.
Avec des index uniques sur la base de données en place, la situation ci-dessus se présentera comme suit.
Réponse tirée de cet article de blog - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations
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