zenet_logo

-株式会社ゼネット技術ブログ-

Rails 7.2のenum非推奨警告を回避する方法と修正ポイント

ゼネットの土屋です。

先日Rails 7.2 Beta 1について投稿させていただきました。
その際、大きな変更点については触れましたが、非推奨への変更については触れませんでした。 media.zenet-web.co.jp

今回はRails 7.2で非推奨となる「enumでキーワード引数を使用した列挙型の定義」について触れていきたいと思います。

非推奨警告の内容

modelのenum定義がキーワード引数を使用した列挙型の場合、下記のような非推奨警告が出力されます。

DEPRECATION WARNING: Defining enums with keyword arguments is deprecated and will be removed
in Rails 7.3. Positional arguments should be used instead:

enum :type, {:unknown=>0, :legacy=>1, :store=>2}
 (called from <class:Agent> at /app/app/models/agent.rb:14)

翻訳すると、

キーワード引数を使用した列挙型の定義は非推奨となり、Rails 7.3では削除される予定です。代わりに位置引数を使用する必要があります。

NG例

問題となるコードを確認してみると下記のようになっていました。

enum type: {:unknown=>0, :legacy=>1, :store=>2}, _prefix: true

非推奨警告に記載されているコード例と比べてみると、些細な違いのようです。

enum type: {:unknown ... # ×
enum :type, {:unknown ... # ○

修正

コード例に倣って下記のように修正してみましたが、別のエラーが出力されました。

enum :type, {:unknown=>0, :legacy=>1, :store=>2}, _prefix: true
Failure/Error: enum :type, { unknown: 0, legacy: 1, store: 2 }, _prefix: true

ArgumentError:
  You tried to define an enum named "type" on the model "Agent", but this will generate a instance method "unknown?", which is already defined by another enum.
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:394:in `raise_conflict_error'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:389:in `detect_enum_conflict!'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:317:in `define_enum_methods'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:285:in `block (2 levels) in _enum'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:279:in `each_pair'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:279:in `each'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:279:in `block in _enum'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:269:in `module_eval'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:269:in `_enum'
# /usr/local/bundle/gems/activerecord-7.2.0.beta1/lib/active_record/enum.rb:219:in `enum'
# ./app/models/agent.rb:14:in `<class:Agent>'

調べてみると、オプションの記述が正しくありませんでした。
キーワード引数での定義の場合は、オプションの先頭に_(アンダースコア)が付きます。(例:_prefix, _default
 参考:https://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html
位置引数での定義の場合は、オプションの先頭に_(アンダースコア)は付けません。(例:prefix, default
 参考:https://api.rubyonrails.org/v7.1.3/classes/ActiveRecord/Enum.html

オプションも修正することで無事に非推奨警告が出力されなくなりました。

enum :type, {:unknown=>0, :legacy=>1, :store=>2}, prefix: true

enumのコードを確認する

enumのコードを読むと分かりますが、第一引数が位置引数かキーワード引数かで処理が分かれていたのですね。

def enum(name = nil, values = nil, **options)
  if name
    values, options = options, {} unless values
    return _enum(name, values, **options)
  end

  definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
  options.transform_keys! { |key| :"#{key[1..-1]}" }

  definitions.each { |name, values| _enum(name, values, **options) }

  ActiveRecord.deprecator.warn(<<~MSG)
    Defining enums with keyword arguments is deprecated and will be removed
    in Rails 7.3. Positional arguments should be used instead:

    #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
  MSG
end

https://github.com/rails/rails/blob/v7.2.0.beta1/activerecord/lib/active_record/enum.rb#L216-L233

なぜこのような分岐が入ったのかコミット履歴を追ってみると、どうやら前述でエラー解決したオプションについて、先頭に_が付いているのをどうにかしたかったようです。

I propose new syntax for enum to avoid leading _ from reserved options, by allowing enum(attr_name, ..., **options) more Attribute API like syntax. github.com

おわりに

enumに関する非推奨警告を対応しました。
非推奨警告が出力されると「なんで今までOKだったのにダメにするんだよ!?」とネガティブな気持ちになりますが、コミット履歴やissueなどから経緯を確認すると納得できるのかなと思います。
また非推奨警告は放置してしまうと、バージョンを上げた際に多量のエラーの原因になったり、はたまた、エラーも吐かずに異なる結果になったりと、よくないことに繋がってしまうので早めの対応をお勧めします。