DiffDaily

Deep & Concise - OSS変更の定点観測

[Rails] enum定義でFloat値をサポート - バリデーション修正による後方互換性の復元

rails/rails

背景と問題

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にアップグレードする際の移行パス