結論
-
form.check_boxはfalse値を送信できない。 - Ruby on Railsでmodelの真偽値(true/false)のvalidationを書くときは、
presence: trueではなくinclusion: [true, false]を使わないといけない。
経緯
Rails をはじめように沿ってrailsを触ってみていました。ただ、そのままやるのはおもしろくないなと思ったため、ガイドでは記事(Article)を表示するウェブページを作成したところを、少しだけアレンジしてTodoを表示するものとして作成していました。TodoはArticleのもつ値に加えてhasDoneという完了済みか否かを表す値を持ちます。
hasDoneはTodoを作成するときに、View側からは
<div>
<%= form.label :hasDone %><br>
<%= form.check_box :hasDone, {checked: false}, true, false %>
</div>
form.check_boxを使って指定するような形にしました。
また、バリデーションも有効化してありました。
class Todo < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
validates :hasDone, presence: true
end
現象
check_boxをチェックすると問題なくレコードが作成されるのですが、check_boxをチェックしていないと、@todo.saveを実行時保存に失敗するようになってしまいました。
問題点
1つ目は、form.check_boxがfalseの値を送信しないことです。これはRailsのcheck_boxのチェック時、アンチェック時にどんな値がサーバに送られるかが詳しいのでこちらを参照ください。
2つ目は、真偽値のバリデーションにpresence:を使ってはいけないことです。Railsガイドのpresenceや公式ドキュメントの該当箇所には、このケースの取り扱い方法が載っています。
presence: trueを指定すると、Railsは内部でfieldが存在するかどうかを.blank?で調査しています。しかし、false.blank?は必ずfalseを返すため、真偽値のバリデーションにpresenceを使ってはならず、かわりにinclusion: [true, false]を使うようにとのことでした。
解決方法
本当はform.check_boxになんとかしてfalse値を送ってもらいたかったのですが、難しそうだったので以下のようにController側で処理することにしました。
def create
# もとのオブジェクトは変更できない
p = todo_params
# フォームの入力がfalseの場合、keyなしのparamsが渡されるため
# validation前にfalseを埋め込む
if !p.respond_to? :hasDone
p[:hasDone] = false
end
@todo = Todo.new(p)
if @todo.save
redirect_to @todo
else
render :new, status: :unprocessable_entity
end
end
private
def todo_params
params.require(:todo).permit(:title, :body, :hasDone)
end
また、model側もドキュメントの通りに変更を加えました。
class Todo < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
validates :hasDone, inclusion: [true, false]
end
この状態でフォームから送信したところ、問題なくレコードが作成されました。
まとめ
- check_boxはfalse値を送信しない。なにかしらのハンドリングや、真偽値の送信に別の方法(0/1など)を使わないといけなさそう。
- modelのvalidationは、それぞれの型にあったものを選ぶ。真偽値の場合は
inclusion: [true, false]。
今回はなにげなく2つ知らなかったことを踏んでいたので、解決にだいぶ時間がかかりました。この記事が私と同じように「ちょっと試してみただけなのに」で大ハマリした人を抜け出せさせられたらなと思います。