結論
カラムに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を使わないようにしましょう。