【Rails】booleanカラムにpresence trueバリデーションをつけて保存時にエラーが発生した
少しハマってしまったのでメモとして残しておく。
結論
boolean型のカラムにバリデーションを設定したいときはこうする。
- validates :published, presence: true
+ validates :published, inclusion: { in: [true, false] }
詳細
以下のようなBlogモデルがあるとする。
class Blog < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
validates :published, presence: true #ここに注目
end
publishedカラムの型は真偽値となっている。
スキーマはこちら。publishedカラムのデフォルト値はfalse。
class CreateBlogs < ActiveRecord::Migration[7.1]
def change
create_table :blogs do |t|
t.string :title, null: false
t.text :body, null: false
t.boolean :published, default: false, null: false
t.timestamps
end
end
end
blogインスタンスを作る。
> blog = Blog.new(title: 'asdf', body: 'adsf', published: false)
=> #<Blog:0x000000012b938718 id: nil, title: "asdf", body: "adsf", published: false, created_at: nil, updated_at: nil>
valid?で評価するとfalseが返る。すべての属性に適正な値をセットしているはずなのに、これはおかしい。trueが返るべきだ。
> blog.valid?
=> false
save!をするとActiveRecord::RecordInvalidが発生して保存できない。
> blog.save!
(irb):6:in `<main>': Validation failed: Published can't be blank (ActiveRecord::RecordInvalid)
Validation failed: Published can't be blankと、publishedカラムがblank(カラムが空の状態)と判定され、エラーが出ている。
しかし、falseというbooleanを代入しているからblankではないはずと思ってしまわないだろうか?
presence: trueバリデーションの実装を見ると、今回の場合だとif value.blank?でtrueが返ってくることからrecord.errors.addが実行されている様子。つまりエラーになる。
つまり、こういうことだ。
> value = false
=> false
> value.blank?
=> true
value変数はfalseであり、それに対してblank?メソッドを実行するとtrueが返る。
ちなみに、逆の場合はfalseが返る。
> value = true
=> true
> value.blank?
=> false
対策
boolean型カラムにバリデーションをかけたい場合、inclusionを使う方法がある。
Active Record バリデーション - Railsガイド
validates :published, inclusion: { in: [true, false] }
# inのエイリアスとしてwithinがある
validates :published, inclusion: { within: [true, false] }
こうすることでインスタンスがvalidにもなるし、無事レコードも保存できる。
> blog = Blog.new(title: 'asdf', body: 'adsf', published: false)
=> #<Blog:0x000000012a1907f0 id: nil, title: "asdf", body: "adsf", published: false, created_at: nil, updated_at: nil>
> blog.valid?
=> true
> blog.save!
TRANSACTION (0.1ms) begin transaction
Blog Create (3.9ms) INSERT INTO "blogs" ("title", "body", "published", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) RETURNING "id" [["title", "asdf"], ["body", "adsf"], ["published", 0], ["created_at", "2024-06-21 08:23:35.542415"], ["updated_at", "2024-06-21 08:23:35.542415"]]
TRANSACTION (0.4ms) commit transaction
=> true
boolean型カラムにはたいていの場合デフォルト値を設定すると思う。もしデフォルト値がfalseだったら、presence :trueバリデーションを書くと保存されなくなるというお話でした。
Discussion