👏

【Rails】enumの例外とバリデーションエラーの順序

2023/04/26に公開

API設計にて、少し困ったことがあったのでメモ。

前提

enum値がAPIで飛んでくるとして、それをそのまま登録するのは楽勝。
では、enumの値以外が飛んできた場合どうするの?普通にバリデーション処理すれば良くね。。。と思っていた。

Railsのenumで範囲外の値を渡すとバリデーションするより先にArgumentErrorが発生してしまう!!

ところが、バリデーションよりも例外が先に発生してしまった。
https://qiita.com/snyt45/items/faf846413d103c68adf9

どうやら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