LoginSignup
0
1

More than 5 years have passed since last update.

Railsで tinyint(1) のカラムにenumを設定すると死ぬ💀

Last updated at Posted at 2018-12-20

結論

カラムにenumを設定したいなら、データ型をtinyint(1)ではいけない。
そもそも2値しか取らないデータにenumを設定すること自体を見直す

環境

Rails5.0からRails5.2.2でのアップグレード作業中に遭遇
※5.1, 5.2のどちらで発生するのかは未検証

Rails5.0ではこう書いてた

Rails5.0で書いていたのはこんなコード

# ユーザテーブル作成
class CreateUser < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.boolean  :display_flg, null: false, default: false
      t.timestamps
    end
  end
end

# ユーザモデル
class User < ApplicationRecord
  enum display_flg: { display: true, not_display: false }
end

ユーザデータに表示/非表示を管理するフラグのカラムを作り、.dispayスコープや、 #display! / #not_display? などのenumが自動生成するメソッドを使いたくてenumを設定していました。

[1] pry(main)> User.create
=> <User:
 id: 1234,
 display_flg: "not_display">

[2] pry(main)> user = User.last
=> <User:
 id: 1234,
 display_flg: "not_display">

[3] pry(main)> user.not_display?
=> "not_display"

[4] pry(main)> user.display_flg_before_type_cast
=> 0

enumにのvalueにbooleanを使っていても問題なく扱えますね

Rails5.2にアップグレードしたら

[1] pry(main)> User.create
=> <User:
 id: 1234,
 display_flg: nil>

[2] pry(main)> user = User.last
=> <User:
 id: 1234,
 display_flg: nil>

[3] pry(main)> user.not_display?
=> false

[4] pry(main)> user.display_flg_before_type_cast
=> 0

[5] pry(main)> user.display!
DailyRecommendRecruitment Update (0.3ms)  UPDATE `users` SET `display_flg` = 1, `updated_at` = '2018-12-20 20:27:57' WHERE `users`.'id' = 1234
=> true

[6] pry(main)> user = User.last
=> <User:
 id: 1234,
 display_flg: nil>

[7] pry(main)> user.display?
=> false

[8] pry(main)> user.display_flg_before_type_cast
=> 1

DBに0で入ったレコードがActiverecordではnilになってしまいました。
かろうじて _before_type_cast を通すとDBの値が取れたり、値を更新するdisplay!は正しく動いているようですが、display?のチェックメソッドは必ずfalseを返してきます。

対策

その1 enumのvalueをbooleanではなくintegerにする

調べる中でこちらの記事にたどり着きました。
https://qiita.com/azyapa/items/1e272dbaef975183efc4

ふむふむ、enumにbooleanは使ってはいけないのか
修正したコードがこちら

# ユーザモデル
class User < ApplicationRecord
  enum display_flg: { display: 1, not_display: 0 }
end

booleanじゃなくて、DBに値そのまま0/1を設定
これで動くはず

[1] pry(main)> User.create
=> <User:
 id: 1234,
 display_flg: nil>

[2] pry(main)> user = User.last
=> <User:
 id: 1234,
 display_flg: nil>

[3] pry(main)> user.not_display?
=> false

[4] pry(main)> user.display_flg_before_type_cast
=> 0

なんでやねん!
どうやらモデルクラスでのenum設定をbooleanからintegerに変えただけではダメそうです

その2 カラムのデータ型を変える

そうはいってもenum設定にintegerを使っていた箇所は多いし、そちらは正常に動いている。
ひょっとしてDBカラムのデータ型が関係している?
データ型をboolean(=tinyint(1))からintegerに変更してみます

class ChangeDisplayFlgToUsers < ActiveRecord::Migration[5.2]
  def up
    change_column :users, :display_flg, :integer
  end
end

今度はどうでしょうか

[1] pry(main)> User.create
=> <User:
 id: 1234,
 display_flg: "not_display">

[2] pry(main)> user = User.last
=> <User:
 id: 1234,
 display_flg: "not_display">

[3] pry(main)> user.not_display?
=> "not_display"

[4] pry(main)> user.display_flg_before_type_cast
=> 0

enumが正常に動くようになりました!
どうやらRailsはDBカラムのデータ型をみて挙動を変えているようです。

そもそも取りうる値が2値しかないデータに対してenumを使っても旨味は少ないので、booleanのカラムにはenumを使わないようにしましょう。

0
1
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
0
1