If you update an existing Rails application that uses MySQL to Rails 6.0, you may get the following warning:
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.
(Translation) Deprecated Warning: Uniqueness validators no longer "force case-sensitive comparisons" in Rails 6.1. If you want to continue using "case sensitive comparison" for the
: name
attribute of the User model, explicitly specify thecase_sensitive: true
option for the uniqueness validator.
The warning comes from the part that uses the uniqueness validator as follows.
class User < ApplicationRecord
validates :name, uniqueness: true
end
For the time being, if you add the case_sensitive
option like this, the warning will disappear.
class User < ApplicationRecord
#This way you won't get any warnings, though! !! !!
validates :name, uniqueness: { case_sensitive: true }
end
However, it is not very good to add options without thinking deeply. So, in this article, I'll go into more detail on how to deal with this warning.
As a premise, this issue occurs when using MySQL. This is usually not a problem if you are using PostgreSQL.
I won't go into details, but MySQL has the concept of collation. The default is a collation such as ʻutf8mb4_unicode_ci`, in which case the strings stored in the database are not case sensitive.
In other words, to search for the name "jnchito", either issuing the SQL WHERE name ='jnchito'
or issuing the SQL WHERE name ='JNCHITO'
will hit.
However, Rails 5.2 and earlier uniqueness validators kindly do case-sensitive comparisons by default.
So, if "jnchito" is already saved in the DB, it behaves as follows.
#Lowercase jnchito is already registered so NG
user.name = 'jnchito'
user.valid? #=> false
#Uppercase jnchito is already unregistered, so OK
user.name = 'JNCHITO'
user.valid? #=> true
#The following SQL is issued behind the scenes (with BINARY)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'JNCHITO' LIMIT 1
At first glance, this looks like a nice specification, but it has the following unexpected disadvantages.
--Validation results are 100% unreliable because they do not match the unique constraints on the DB --The load on the DB increases because INDEX on the DB cannot be used efficiently.
In fact, the code I just mentioned behaves inconsistently as follows: (When a unique constraint is attached to the DB side)
#Uppercase"JNCHITO"Then it seems that it can be saved because there is no verification error
user.name = 'JNCHITO'
user.valid? #=> true
#Save execution ... Oh, I got caught in a DB unique constraint violation and an exception occurred! !!
user.save
#=> ActiveRecord::RecordNotUnique:
# Mysql2::Error: Duplicate entry 'JNCHITO' for key 'users.index_users_on_name'
It seems that this kind of problem often occurred when using MySQL with Rails. (I didn't notice it because I usually use PostgreSQL)
To work around this issue, the Rails 6.1 uniqueness validator is case insensitive by default. Rather, strictly speaking, the specification is that "Rails issues SQL obediently and leaves the case distinction to the DB side settings".
As a result, the functions on the DB side can be fully utilized, so the above mentioned,
--Validation results are 100% unreliable because they do not match the unique constraints on the DB --The load on the DB increases because INDEX on the DB cannot be used efficiently.
Such problems will not occur.
For example, if you already have "jnchito" stored in your DB, Rails 6.1 will probably behave like this:
#jnchito is already registered so NG (case insensitive)
user.name = 'jnchito'
user.valid? #=> false
#JNCHITO is already registered, so NG (case insensitive)
user.name = 'JNCHITO'
user.valid? #=> false
#The following SQL should be issued behind the scenes (without BINARY)
# SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'JNCHITO' LIMIT 1
However, the Rails 6.1 specification change, in exchange for avoiding "unexpected disadvantages", leads to a change in behavior of "case insensitive".
So, Rails 6.0 keeps the behavior of Rails 5.2 and earlier, but encourages developers to change it, saying, "Rails 6.1 will change its behavior! Decide what you want to do now!". That is the warning introduced at the beginning.
If you want to be case sensitive as in the Rails 5.2 era, you can explicitly add the case_sensitive: true
option and the warning will disappear.
However, if there is no change in the collation on the DB side,
--Validation results are 100% unreliable because they do not match the unique constraints on the DB --The load on the DB increases because INDEX on the DB cannot be used efficiently.
I will still have the problem.
class User < ApplicationRecord
#No warning will be issued, but "unexpected disadvantages" will remain unless the collation on the DB side is changed.
validates :name, uniqueness: { case_sensitive: true }
end
If you want to solve these problems, you need to change the collation on the DB side to "case sensitive collation" like ʻutf8mb4_bin` instead of modifying the code on the Rails side. (The procedure for changing the collation is omitted here.)
If the collation on the DB side is case sensitive, the warning will not be issued because there will be no mismatch with the behavior of Rails' uniqueness validator. (You do not need to specify the case_sensitive
option)
class User < ApplicationRecord
#If you change the collation on the DB side, case_No sensitive option required. No warnings or "unexpected disadvantages"
validates :name, uniqueness: true
end
Explicitly specify case_sensitive: false
if you do not need to be case sensitive.
This way, neither the collation on the DB side nor the Rails uniqueness validator is case sensitive, so the mismatch will be resolved and no warning will be displayed.
However, in this case, the behavior of the application will change, so it is necessary to carefully consider whether it will cause confusion for the user.
class User < ApplicationRecord
#No warning will be issued. There are no "unexpected disadvantages". But Rails 5.Behavior changes with 2
validates :name, uniqueness: { case_sensitive: false }
end
You can also remove the case_sensitive: false
option after upgrading your application to Rails 6.1. (Because it is not case sensitive by default)
class User < ApplicationRecord
# Rails 6.1 case_OK even if you lose sensitivity
validates :name, uniqueness: true
end
This story changes depending on the combination of "collation on the MySQL side", "uniqueness validator'case_sensitive` option" and "Rails version".
The table below summarizes what happens with each combination.
Ultimately, it will be an ideal state if the combination that the "DB side and Rails mismatch?" Column in the above table becomes "NO" can be realized.
-Three behaviors of Active Record that become Deprecated in Rails 6 \ .0 -Kamipowar
I asked kamipo, a Rails committer, about this question on Twitter and answered politely (Reference). Thank you very much, kamipo!