🚨

【Rails】presence: true にしつつ、空文字を許容したい

2024/08/07に公開

こんにちは!
ラブグラフエンジニアのひろです。

今回はバリデーションについてです。
「値が入っていて欲しいので presence: true にしたいが、既存のデータには "" が入っている」
という状況に対応できるバリデーションを作っていきます。

問題の説明

例えば、 users テーブルの name カラムを必須にしたいケースで考えてみましょう。
単純に考えれば presence: true を追加することになると思います。
これにより新しく User を作るとき、 name が指定されていなければバリデーションエラーが発生してくれます。

class User < ApplicationRecord
  validates :name, presence: true
end
user = User.new(name: nil)
user.valid?
# => false
user.errors[:name]
# => ["can't be nil"]

しかし、すでに存在する User の name カラムに ""(空文字) が入っているとどうなるでしょうか?
presence: true は内部的に .blank? を使ってバリデーションをおこなうため、既存 User にもバリデーションエラーが発生するようになってしまいます。

existing_user = User.find_by(name: "")
existing_user.valid?
# => false
existing_user.errors[:name]
# => ["can't be blank"]

allow_blank との併用

では presence: true と、 空文字や nil を許可する allow_blank: true を併用するとどうなるでしょうか?

class User < ApplicationRecord
  validates :name, presence: true, allow_blank: true
end

実際に Rails コンソールで確認してみましょう。

user = User.new(name: "")
user.valid?
# => true

user.name = nil
user.valid?
# => true

この設定では、 allow_blank: true が優先され、 presence: true があるにもかかわらず、空文字や nil も登録することができるようになってしまいます。

nil だけを弾くバリデーションの実装方法

され、それでは今回のケースに対応するバリデーションを作っていきましょう。

空文字を許容して、nil だけを弾くには、カスタムバリデーションを使います。
以下は User モデルでのカスタムバリデーションの例です。

class User < ApplicationRecord
  validate :name_cannot_be_nil

  private

  def name_cannot_be_nil
    errors.add(:name, "can't be nil") if name.nil?
  end
end

このカスタムバリデーションでは、 namenil の場合にだけエラーを追加します。
空文字は許容されるので、 name が空文字の既存 User を更新する際に影響は与えません。

実装後の確認方法

この実装がちゃんと動くかを確認するために、 Rails コンソールでチェックします。

user = User.new(name: "")
user.valid?
# => true

user.name = nil
user.valid?
# => false

name が空文字の場合にはバリデーションエラーが発生せず、 nil の場合には発生することが確認できました。

まとめ

この記事では、 presence: true を使った場合に、空文字もバリデーションエラーになる問題について説明し、nil だけを弾くカスタムバリデーションの方法を紹介しました。
また、 presence: trueallow_blank: true の併用が意味をなさないことも理解してもらえたと思います。

バリデーションの設定で悩んだことがあるエンジニアの皆さんに、役立つ情報になれば嬉しいです。

ラブグラフのエンジニアブログ

Discussion