What to do if you get the warning "Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1." in Rails 6.0

Introduction

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 the case_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.

Rails 5.2 and earlier specifications (and problems)

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)

Specifications introduced in Rails 6.1 (rather than Rails 6.0)

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

Rails 6.0 encourages developers to review their code and DB settings for a 6.1 spec change

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.

Case sensitive

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

Case insensitive

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

Reference: Behavior summary of Rails 5.2-6.1

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.

Screen Shot 2020-05-28 at 11.14.41.png

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.

References

-Three behaviors of Active Record that become Deprecated in Rails 6 \ .0 -Kamipowar

Acknowledgments

I asked kamipo, a Rails committer, about this question on Twitter and answered politely (Reference). Thank you very much, kamipo!

Recommended Posts

What to do if you get the warning "Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1." in Rails 6.0
Remedy for "Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1."
What to do if you can't get the text of an element in Selenium
What to do if you get a java.io.IOException in GlassFish
What to do if you can't use the rails command
[Rails] What to do if you accidentally install bundle in the production environment in your local environment
[Rails] What to do if you can't get an error message with the errors method
What to do if you get a "302" error in your controller unit test code in Rails
What to do if you get an error in Basic authentication during Rails test code
What to do if you get a gcc error in Docker
What to do if the Rails page doesn't appear in Rails tutorial 1.3.2
What to do if you get a DISPLAY error in gym.render ()
What to do if you forget the root password in CentOS7
[Rails] What to do if you can't get parameters with form_with
What to do if you get a "Mysql2 :: Error: Operand should contain 1 column (s)" error in Rails
What to do if you get an error during rails db: reset
What to do if you can't bundle update and bundle install after installing Ruby 3.0.0 in the Rails tutorial
What to do if you get the error Too long with no output (exceeded 10m0s) on CircleCI
What to do if you get the error message unrecognized selector send to instance "***"
What to do if you get an "A server is already running." Error when you try to start the rails server
What to do if you get a javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake in the IBM JDK
What to do if you get a wrong number of arguments error in binding.pry
What to do if you get the error Couldn't find Item without an ID
# What to do if you accidentally do rails db: migrate: drop
What to do if you don't see the test code error message in the terminal console
[Rails Tutorial Chapter 2] What to do when you make a mistake in the column name
What to do if you select a JRE in Eclipse and get "The selected JRE does not support the current compliance level 11"
What to do if you get further occurrences of http header parsing errors will be logged at debug level. In tomcat 8.5.37
[Rails] What to do when the error No database selected and Unknown database appears in db: migrate
[Rails] What to do if data is not registered in DB
[Note] What to do if bundle install in Chapter 3 of the rails tutorial is not possible
What to do if you get angry with OpenSSL with pyenv install
What to do if you cannot roll back the migration (UnknownMigrationVersionError)
What to do if you get an error with bundle install by entering gem'bcrypt' in your Gemfile
What to do if the image posted by refile disappears after setting a 404 error page in Rails
What to do if Operation not permitted is displayed when you execute a command in the terminal
What to do if the prefix c is not bound in JSP
What to do if you get an uninitialized constant Likes Controller error
What to do if you can't install the plugin from the Eclipse marketplace
What to do if you don't like the code generated by swagger-codegen-cli
What to do if you get an error when you hit Heroku logs
What to do if you get a MiniMagick vulnerability alert on GitHub
What to do if you install Ubuntu
[Rails] What to do if you get an error saying "Could not find a JavaScript runtime." When executing the rails s command on Catalina
What to do if you have enabled Use the WSL2 based engine in Docker Desktop with insufficient WSL2 installation
What to do if you get Could not save master table to file after importing a project in Eclipse
How to translate the error message into Japanese (What to do if you cannot log in for some reason)
What to do if you get a SQLite3 :: BusyException: database is locked error
What to do if you get Could not locate Gemfile or .bundle / directory
What to do if you can't activate the select box created by bootstrap-select
What to do when the value becomes null in the second getSubmittedValue () in JSF Validator
What to do when rails db: seed does not reflect in the database
What to do if you get an error on heroku rake db: migrate
What to do if you get angry if you don't have nokogiri while installing wp2txt
What to do if the rails server doesn't run out on AWS cloud9
What to do if ffi installation fails when launching an application in Rails
What to do if the server tomcat dies
What to do if you push incorrect information
[Java] What to do if you get an error in Eclipse saying "Not allowed at source level below 1.X"
What to do if you get an Argument Error: wrong number of arguments (given 2, expected 0) in your RSpec test
What to do if you get an error saying "Please enter a valid value" when getting with Rails datetime_field