LoginSignup
18
11

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-04

例えば、次のようなユーザーモデルがあるとします。

user.rb
class User < ApplicationRecord
  enum gender: { male: 1, female: 2, other: 3}
  validates :gender, inclusion: { in: User.genders.keys }
end

このユーザーモデルに対して、enumで指定していない値(今回はunknown)を渡すと、バリデーションエラーより先にArgumentErrorが発生してしまいます

user = User.new(gender: 'unknown') # ArgumentError ('unknown' is not a valid gender)

Railsのissueをみると、これは仕様のようです。
参考:https://github.com/rails/rails/issues/13971

現在のAR enumsの焦点は、パフォーマンス上の理由から、状態のセット(ラベル)を整数にマッピングすることです。現在のところ、間違った状態の割り当てはアプリケーションレベルのエラーとみなされ、ユーザー入力のエラーではありません。これが ArgumentError を取得する理由です。

issueを見ていくと解決策として、gem(ex. enumerize gem、enum_attributes_validation)の導入でも解消できそうです(実際にgemの検証はしていません)。

私の経験では、ARの列挙型は使用に直面する属性として多く使われています。コミュニティにはenumの問題を解決するいくつかのジェムがあります(例:enumerize gemや上記のenum_attributes_validation)。しかし、私はrailsがこれらの問題を解決し、箱から出してより良い経験を提供すべきだと考えています。喜んでその問題に協力します。

ですが、今回解消したいRailsプロジェクトでは初期段階からRailsのデフォルト機能のActiveRecord::Enumを使っているため、影響範囲等を考えgemは使わずに別の方法で解消していきたいと思います。

解決策

先ほどと同じissueにあった方法です。
Rails6で試して正常に動きました。
参考:https://github.com/rails/rails/issues/13971#issuecomment-542757073

user.rb
# app/models/user.rb
class User < ApplicationRecord
  include LiberalEnum # ← 追加

  enum gender: { male: 1, female: 2, other: 3}
  liberal_enum :gender # ← 追加

  validates :gender, inclusion: { in: User.genders.keys }
end
liberal_enum.rb
# app/models/concerns/liberal_enum.rb
module LiberalEnum
  extend ActiveSupport::Concern

  class_methods do
    def liberal_enum(attribute)
      decorate_attribute_type(attribute, :enum) do |subtype|
        LiberalEnumType.new(attribute, public_send(attribute.to_s.pluralize), subtype)
      end
    end
  end
end
liberal_enum.rb
# app/types/liberal_enum.rb
class LiberalEnumType < ActiveRecord::Enum::EnumType
  # suppress <ArgumentError>
  # returns a value to be able to use +inclusion+ validation
  def assert_valid_value(value)
    value
  end
end

コードを追加した後は、サーバーを再起動する必要があります。

new時にArgumentErrorが発生せずにバリデーションを実行してくれるようになりました!

user = User.new(gender: 'unknown')
user.save! # ActiveRecord::RecordInvalid (バリデーションに失敗しました:  性別は一覧にありません)

まとめ

  • enumで範囲外の値を渡すとArgumentErrorが発生するのはRailsの仕様
  • API等で何でも値を渡せる状況の場合は、プロジェクト初期段階からgem(ex. enumerize gem、enum_attributes_validation)を導入しておくとこういう問題で悩まなくてよさそう ※実際にgemの検証はしていません。
18
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
11