Si vous mettez à jour une application Rails existante qui utilise MySQL vers Rails 6.0, vous pouvez recevoir l'avertissement suivant:
DEPRECATION WARNING: Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1. To continue case sensitive comparison on the :name attribute in User model, pass `case_sensitive: true` option explicitly to the uniqueness validator.
(Traduction) Avertissement obsolète: Unicité Varidata ne "force plus les comparaisons sensibles à la casse" dans Rails 6.1. Si vous souhaitez continuer à utiliser la "comparaison sensible à la casse" pour l'attribut
: name
du modèle User, spécifiez explicitement l'optioncase_sensitive: true
pour le validateur d'unicité.
L'avertissement apparaît dans la partie qui utilise le validateur d'unicité comme suit.
class User < ApplicationRecord
validates :name, uniqueness: true
end
Pour le moment, si vous ajoutez l'option case_sensitive
comme celle-ci, l'avertissement disparaîtra.
class User < ApplicationRecord
#De cette façon, vous ne recevrez aucun avertissement! !! !!
validates :name, uniqueness: { case_sensitive: true }
end
Cependant, il n'est pas très bon d'ajouter des options sans réfléchir profondément. Donc, dans cet article, je vais entrer plus en détail sur la façon de traiter cet avertissement.
En principe, ce problème se produit lors de l'utilisation de MySQL. Ce n'est généralement pas un problème si vous utilisez PostgreSQL.
Je n'entrerai pas dans les détails, mais MySQL a le concept de collaboration. La valeur par défaut est une collaboration telle que ʻutf8mb4_unicode_ci`, auquel cas les chaînes stockées dans la base de données ne sont pas sensibles à la casse.
En d'autres termes, pour rechercher le nom "jnchito", soit en émettant le SQL WHERE name = 'jnchito'
ou en émettant le SQL WHERE name =' JNCHITO'
frappera.
Cependant, Rails 5.2 et les validateurs d'unicité antérieurs font par défaut des comparaisons sensibles à la casse.
Donc, si "jnchito" est déjà enregistré dans la base de données, il se comporte comme suit.
#Lower jnchito est déjà enregistré, donc NG
user.name = 'jnchito'
user.valid? #=> false
#Le jnchito supérieur n'est déjà pas enregistré, donc c'est OK
user.name = 'JNCHITO'
user.valid? #=> true
#Le SQL suivant est émis dans les coulisses (avec BINARY)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'JNCHITO' LIMIT 1
À première vue, cela ressemble à une belle spécification, mais elle présente les inconvénients inattendus suivants.
En fait, le code que je viens de mentionner se comporte de manière incohérente comme suit: (Lorsqu'une contrainte unique est attachée au côté DB)
#Capitale"JNCHITO"Ensuite, il semble qu'il puisse être enregistré car il n'y a pas d'erreur de vérification
user.name = 'JNCHITO'
user.valid? #=> true
#Sauver l'exécution ... Oh, je me suis fait prendre dans une violation de contrainte unique DB et une exception s'est produite! !!
user.save
#=> ActiveRecord::RecordNotUnique:
# Mysql2::Error: Duplicate entry 'JNCHITO' for key 'users.index_users_on_name'
Il semble que ce genre de problème se soit souvent produit lors de l'utilisation de MySQL avec Rails. (Je ne l'ai pas remarqué car j'utilise habituellement PostgreSQL)
Pour contourner ce problème, les validateurs d'unicité de Rails 6.1 sont insensibles à la casse par défaut. Au contraire, à proprement parler, la spécification est que «Rails émet SQL docilement et laisse la distinction de cas aux paramètres côté DB».
En conséquence, les fonctions du côté DB peuvent être pleinement utilisées, de sorte que ce qui précède,
De tels problèmes ne se produiront pas.
Par exemple, si vous avez déjà "jnchito" stocké dans votre base de données, Rails 6.1 se comportera probablement comme ceci:
#NG (insensible à la casse) car jnchito est déjà enregistré
user.name = 'jnchito'
user.valid? #=> false
#JNCHITO est déjà enregistré, donc NG (insensible à la casse)
user.name = 'JNCHITO'
user.valid? #=> false
#Le SQL suivant doit être émis dans les coulisses (sans BINARY)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'JNCHITO' LIMIT 1
Cependant, en échange d'éviter des «inconvénients inattendus», le changement de spécification de Rails 6.1 entraîne un changement dans le comportement «d'être insensible à la casse».
Ainsi, Rails 6.0 conserve le comportement de Rails 5.2 et versions antérieures, mais encourage les développeurs à changer, en disant: "Rails 6.1 changera son comportement! Décidez de ce que vous voulez faire maintenant!". C'est l'avertissement introduit au début.
Si vous voulez être sensible à la casse comme à l'époque de Rails 5.2, vous pouvez ajouter explicitement l'option case_sensitive: true
et l'avertissement disparaîtra.
Cependant, s'il n'y a pas de changement dans la collaboration côté DB,
J'aurai toujours le problème.
class User < ApplicationRecord
#Aucun avertissement ne sera émis, mais si vous ne modifiez pas la collaboration côté DB, des «inconvénients inattendus» subsisteront.
validates :name, uniqueness: { case_sensitive: true }
end
Si vous voulez résoudre ces problèmes, vous devez changer la collaboration côté base de données en "collaboration sensible à la casse" comme ʻutf8mb4_bin` au lieu de modifier le code côté Rails. (La procédure de modification de la collaboration est omise ici)
Si la collaboration côté DB est sensible à la casse, l'avertissement ne sera pas émis car il n'y aura pas d'incohérence avec le comportement du validateur d'unicité de Rails. (Vous n'avez pas besoin de spécifier l'option case_sensitive
)
class User < ApplicationRecord
#Si vous modifiez la collaboration côté DB, cas_Aucune option sensible requise. Aucun avertissement ou "inconvénients inattendus"
validates :name, uniqueness: true
end
Spécifiez explicitement case_sensitive: false
si vous n'avez pas besoin d'être sensible à la casse.
De cette façon, ni la collaboration côté base de données ni le validateur d'unicité de Rails ne seront insensibles à la casse, donc l'incohérence sera résolue et aucun avertissement ne sera affiché.
Cependant, dans ce cas, le comportement de l'application changera, il est donc nécessaire d'examiner attentivement si cela va créer de la confusion pour l'utilisateur.
class User < ApplicationRecord
#Aucun avertissement ne sera émis. Il n'y a pas de "désavantages inattendus". Mais Rails 5.Changements de comportement avec 2
validates :name, uniqueness: { case_sensitive: false }
end
Vous pouvez également supprimer l'option case_sensitive: false
après la mise à niveau de votre application vers Rails 6.1. (Parce qu'il est insensible à la casse par défaut)
class User < ApplicationRecord
# Rails 6.1 cas_OK même si vous perdez la sensibilité
validates :name, uniqueness: true
end
Cette histoire change en fonction de la combinaison de «classement côté MySQL», «option de validation d'unicité« case_sensitive »» et «version Rails».
Le tableau ci-dessous résume ce qui se passe avec chaque combinaison.
En fin de compte, il sera dans un état idéal si une combinaison dans laquelle la colonne "côté DB et Rails ne correspondent pas?" Dans le tableau ci-dessus devient "NON" peut être réalisée.
Nous avons demandé à kamipo, membre du comité Rails, de répondre attentivement à cette question sur Twitter (Référence). Merci beaucoup, kamipo!