👏
【Rails】enumの例外とバリデーションエラーの順序
API設計にて、少し困ったことがあったのでメモ。
前提
enum値がAPIで飛んでくるとして、それをそのまま登録するのは楽勝。
では、enumの値以外が飛んできた場合どうするの?普通にバリデーション処理すれば良くね。。。と思っていた。
Railsのenumで範囲外の値を渡すとバリデーションするより先にArgumentErrorが発生してしまう!!
ところが、バリデーションよりも例外が先に発生してしまった。
どうやらRailsではバリデーションチェックよりenumの例外が先に発生するっぽい。
エラーをまとめて返したい。
今回のAPIの要件として、エラーが起きた場合全てのエラーメッセージをまとめて返す必要がある。
つまり、例外発生時のメッセージ + バリデーションエラーメッセージをまとめて配列に返すという仕様。
その場合どうすれば良いだろうか。
サンプルコード
少しサンプルを書いてみた。
例えば以下のようなUserモデルがあるとする。
class User < ApplicationRecord
enum status: { active: 1, archived: 2 }
validates :status, presence: true
validates :name, presence: true
end
そのUserモデルに対して、enumの範囲外の値とバリデーションに違反するような値をそれぞれいれてやる。
user = User.new(status: :invalid_status, name: nil)
begin
user.save!
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors.full_messages
rescue ArgumentError => invalid
puts invalid.message
user.valid?
puts user.errors.full_messages
end
この例では、newメソッドでstatusにinvalid_statusを指定して、enumの例外を発生させる。その後、nameにnilを指定して、バリデーションエラーも発生させる。
save!メソッドを呼び出すと、まずArgumentError例外が発生し、その例外メッセージを出力した後、user.valid?メソッドを呼び出して、バリデーションチェックを行う。そして、puts user.errors.full_messagesによって、バリデーションエラーの詳細を出力。
この例では、puts invalid.messageによって、enumの例外のメッセージも出力される。
最終的に出力されるメッセージは、以下のようだ。
invalid value for Integer(): "invalid_status"
Name can't be blank
これでまとめて返すこともできそうだ。
まとめ
Railsの挙動なのかどの言語でもこういった挙動なのか気になる。。。
Discussion