はじめに
今回は、「アイテム出品時と同様に、編集する際にも最終確認画面を一枚挟みたい」というイシューを開発中に経験した思わぬ落とし穴について書いていきます!
起こったエラー
itemに紐づいた、item_optionのカラム(ここでは仮にexchange_to_point(boolean)とする)を変更する際に、最終確認画面にて
= f.fields_for :item_option do |ff|
= ff.hidden_field :exchange_to_point, value: f.object.exchange_to_point
のようにitemに向けたformでitem.item_option.exchange_to_pointを渡し、updateアクションを呼ぼうとした際に正常に更新がされなかった。
エラー内容
Mysql2::Error: Column 'exchange_to_point' cannot be null
item_option_paramsではexchange_to_pointをpermitしていたのだが、paramsを確認すると "item_option"=>{"exchange_to_point"=>""} と本来not nullのexchange_to_pointが渡ってきていなかった。
検証&結果
f.object.exchange_to_pointの内容をdebugしてみると"false"が返ってきた。一見正常に見えたのだが、フォームタグはinputタグを生成する際にvalueとしてstring型で値を渡すため、booleanの型に正しくハマらずnilとなってしまっていたのである。
実験を行った結果、<input value="0"> で更新しようとすると成功、<input value="false"> で更新しようとすると失敗する、ということが分かった。
また、item新規作成の際は
value: f.object.exchange_to_point
で<input value="0">になるのに対して、item更新時は
value: f.object.exchange_to_point ? 1 : 0
と書かないと<input value="false">になってしまうということも分かった。
それらの不思議な挙動について調査したところ、原因は「MySQLではbooleanはTINYINT(1)型で管理、すなわち[0,1]で処理されている」のに対して、「Railsではfalseとnilのみがfalse判定であり、*FALSE_VALUESは内部的にfalse に変換して扱われる」という違いにあった。
FALSE_VALUES = [ false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF, ]
今回は新規登録の際にはformからの0,1が、更新時はActiveRecordのfalse,trueが渡されるため、htmlに変換した際に上記のような差が生じてしまうと考えられる。
まとめ
・フォームのvalueではbooleanであってもすべてstringに変換されるため、"false"などを渡すと正しくMySQLのbooleanが更新されないことがあるため、"0"などの値で渡すほうが安全。(MySQLではbooleanを[1, 0]で管理)
・フォームのvalueに"0"を渡すためには、ActiveRecordからデータを持ってくる際(既存レコードを更新する際など)にはRailsではbooleanを[true, false]で管理しているため明示的に[1, 0]に整形してあげる必要がある。