結論
-
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つ知らなかったことを踏んでいたので、解決にだいぶ時間がかかりました。この記事が私と同じように「ちょっと試してみただけなのに」で大ハマリした人を抜け出せさせられたらなと思います。