44
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails6 のちょい足しな新機能を試す81(case_sensitive編)

Last updated at Posted at 2019-09-19

はじめに

Rails 6 に追加された新機能を試す第81段。 今回は、 case_sensitive 編です。
Rails 6 では、 MySQL を使っているときに、 uniqueness の validation を使っているときに警告が出るようになりました。

Ruby 2.6.4, Rails 6.0.0, MySQL 8.0.17 で確認しました。

$ rails --version
Rails 6.0.0

今回は、 User モデルを作成して rails console を使って確認します。

プロジェクトを作る

rails new rails_sandbox
cd rails_sandbox

User モデルを作る

name の属性をもつ User モデルを作ります。

bin/rails g model User name

User モデルにバリデーションをつける

User の name 属性に uniqueness 属性をつけます。

app/models/user.rb
class User < ApplicationRecord
  validates :name, uniqueness: true
end

seed データを登録する

User を1件だけ登録します。

db/seed.rb
User.create(name: 'Taro')

マイグレーションを実行し seed データを登録する

bin/rails db:create db:migrate db:seed

rails console で確認する

rails console で確認します。

User.new(name: 'taro') で user オブジェクトを作ります。

irb(main):001:0> user = User.new(name: 'taro')
   (0.4ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, name: "taro", created_at: nil, updated_at: nil>

user.valid? で validation を実行します。

irb(main):002:0> user.valid?
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. (called from irb_binding at (irb):2)
  User Exists? (0.9ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'taro' LIMIT 1
=> true

DEPRECATION WARNING が表示されることがわかります。
また、 validation のチェックをする SQL の WHERE 句 が

WHERE `users`.`name` = BINARY 'taro'

BINARY がついていることに注意してください。

この動作の問題点

user を保存します。

irb(main):003:0> user.save
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. (called from irb_binding at (irb):3)
   (0.5ms)  BEGIN
  User Exists? (0.7ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'taro' LIMIT 1
  User Create (0.7ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('taro', '2019-09-01 04:37:55.708725', '2019-09-01 04:37:55.708725')
   (13.0ms)  COMMIT
=> true

name が taro であるデータを検索します。

irb(main):006:0> User.where(name: 'taro').count
   (0.8ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`name` = 'taro'
=> 2

2件見つかりました。name が Tarotaro の両方のデータが検索されてしまうのです。

という訳で

Rails 6.1 以降では、この不統一な動作をやめて、デフォルトでは、大文字小文字を区別しない validation になるのではないかと思われます。
これまで通りの動作をさせたい場合には、 :case_sensitive オプションを使います。

:case_sensitive を true にしたとき

app/models/user.rb
class User < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: true }
end

これで validation のチェックをしてみましょう。

irb(main):006:0> reload!
Reloading...
=> true
irb(main):007:0> user = User.new(name: 'taRo')
=> #<User id: nil, name: "taRo", created_at: nil, updated_at: nil>
irb(main):008:0> user.valid?
  User Exists? (0.8ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'taRo' LIMIT 1
=> true

こんどは、ワーニングが出ないようになりました。

:case_sensitive を false にしたとき

app/models/user.rb
class User < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: false }
end

これで validation のチェックをしてみましょう。

irb(main):009:0> reload!
Reloading...
=> true
irb(main):010:0> user = User.new(name: 'taRo')
=> #<User id: nil, name: "taRo", created_at: nil, updated_at: nil>
irb(main):011:0> user.valid?
  User Exists? (0.8ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'taRo' LIMIT 1
=> false

今度は、警告は出ないですが、 validation も大文字小文字の区別をしないでチェックしていることに注意してください。
user.valid? の結果が false ですし、validation で使われる SQL の where 句も BINARY がついてません。

そもそも MySQL で大文字小文字の区別をするようにしていたらどうなるか?

では、先に MySQL 側で大文字小文字を区別するように、migration が実行されていたらどうなるか、確認してみましょう。

$ bin/rails g migration user_name_column_to_binary

できたマイグレーションファイルを編集します。

db/migrate/20190901051514_user_name_column_to_binary.rb
class UserNameColumnToBinary < ActiveRecord::Migration[6.0]
  def up
    execute('ALTER TABLE users MODIFY name varchar(255) BINARY')
  end

  def down
    execute('ALTER TABLE users MODIFY name varchar(255)')
  end
end

model は以下のように戻しておきます。

app/models/user.rb
class User < ApplicationRecord
  validates :name, uniqueness: true
end

マイグレーションを実行し、seed データを登録し直します。

$ bin/rails db:migrate db:seed:replant

rails console で改めて確認します。

irb(main):001:0> user = User.new(name: 'taro')
   (0.3ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, name: "taro", created_at: nil, updated_at: nil>

valid? メソッドを実行しても警告はでません。

irb(main):002:0> user.valid?
  User Exists? (0.4ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'taro' LIMIT 1
=> true

user を保存して、 taro を検索すると 1件です。

irb(main):003:0> user.save
   (0.5ms)  BEGIN
  User Exists? (0.7ms)  SELECT 1 AS one FROM `users` WHERE `users`.`name` = 'taro' LIMIT 1
  User Create (0.3ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('taro', '2019-09-01 05:32:29.403443', '2019-09-01 05:32:29.403443')
   (13.0ms)  COMMIT
=> true
irb(main):004:0> User.where(name: 'taro').count
   (0.7ms)  SELECT COUNT(*) FROM `users` WHERE `users`.`name` = 'taro'
=> 1

この場合は、警告もなく、矛盾なく動作しているようです。

蛇足ですが、 カラム単位でなくテーブル単位で大文字小文字を区別する設定もできるようです(が未確認です)。 
また、 MySQL の DB 自体 ( config/database.yml ) でまとめて大文字小文字を区別する設定する方法もありそうな気がするのですが、見つけられませんでした。

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try081_mysql_comparison

MySQL で大文字小文字の区別をするようにしていた場合
https://github.com/suketa/rails_sandbox/tree/try081_mysql_comparison_user_name_column_to_binary

参考情報

44
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?