[Rails] enum定義でFloat値をサポート - バリデーション修正による後方互換性の復元
背景と問題
Rails 8.1.1で、enum定義にFloat値を使用するとArgumentErrorが発生する問題が報告されました。これは、Rails 7.0.6、7.2.3、8.0.4では正常に動作していた機能が、Rails 8.1.0で突然動作しなくなったことを意味します。
enum :rating, { low: 0.0, medium: 0.5, high: 1.0 }, prefix: true
# => ArgumentError: Enum values {:low=>0.0, :medium=>0.5, :high=>1.0} must be only booleans, integers, floats, symbols or strings, got: Float
この問題は、Rails 8.1.0でシンボル型のenum値をサポートするために追加されたバリデーション処理(2dc080f)において、Float型が許可リストに含まれていなかったことが原因です。データベースに既にFloat値のenumを保存しているアプリケーションにとって、これは深刻な後方互換性の破壊となります。
技術的な変更内容
バリデーション処理の修正
ActiveRecord::Enum#assert_valid_enum_definition_valuesメソッドのcase文にFloatを追加し、enum値の型チェックでFloat型を許可するようにしました。
変更前:
values.each_value do |value|
case value
when String, Integer, true, false, nil
# noop
else
raise ArgumentError, "Enum values #{values} must be only booleans, integers, symbols or strings, got: #{value.class}"
end
end
変更後:
values.each_value do |value|
case value
when String, Integer, Float, true, false, nil
# noop
else
raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
end
end
エラーメッセージも「floats」を含む形に修正され、ユーザーに対して許可される型を正確に伝えるようになりました。
テストの追加
Float値を使用したenumの動作を検証するテストケースが追加されました。
test "enum with float values" do
klass = Class.new(ActiveRecord::Base) do
self.table_name = "books"
enum :rating, { low: 0.0, medium: 0.5, high: 1.0 }, prefix: true
end
instance = klass.new
instance.rating_medium!
assert_predicate instance, :rating_medium?
assert_equal 0.5, instance.rating_for_database
assert_equal "medium", instance.rating
end
このテストは以下を確認します:
- Float値を使用したenum定義が成功すること
- enum値の設定・取得が正常に動作すること
- rating_for_databaseメソッドが正しいFloat値を返すこと
- ratingメソッドがenum名(文字列)を返すこと
対応するマイグレーションとして、booksテーブルにratingカラム(float型)も追加されています。
影響範囲
この修正により、Rails 8.1.0で破壊された後方互換性が復元されます。特に以下のケースで恩恵があります:
- 評価システム(0.0~1.0の範囲など)でFloat値のenumを使用しているアプリケーション
- 既存データベースに小数値でenumが保存されているレガシーアプリケーション
- Rails 7.xからRails 8.1にアップグレードする際の移行パス