例えば、次のようなユーザーモデルがあるとします。
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
# 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
# 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
# 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の検証はしていません。