💭

【Rails】booleanカラムにpresence trueバリデーションをつけて保存時にエラーが発生した

2024/06/21に公開

少しハマってしまったのでメモとして残しておく。

結論

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カラムの型は真偽値となっている。

スキーマはこちら。

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が返る。

> blog.valid?
=> false

save!をするとActiveRecord::RecordInvalidが発生して保存できない。

> blog.save!
(irb):6:in `<main>': Validation failed: Published can't be blank (ActiveRecord::RecordInvalid)

一見すると、falseというbooleanを代入しているからblankではないはずと思える。

presence: trueバリデーションの実装を見ると、今回の場合だとif value.blank?trueが返ってくることからrecord.errors.addが実行されている様子。つまりエラーになる。

https://github.com/rails/rails/blob/16d8b82d5e2aca4780c5d10b1fe9a90b33a0e84e/activemodel/lib/active_model/validations/presence.rb#L3-L9

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