Rails 7.1/8.x対応: ActiveSupport::Deprecationで実装するdeprecation warningの完全ガイド【コード例付き】

2013年5月24日


Railsアプリやgemを開発していると、古いメソッドや非推奨APIに警告を出しつつ段階的に廃止したい場面が必ず訪れる。しかしRails 7.1でActiveSupport::Deprecationのシングルトン委譲が廃止され、Rails 8.0でさらに削除が進んだことで、以前の実装パターンが完全に動作しなくなっている。

このガイドでは、Rails 7.1以降の正しいdeprecation warning実装方法を、実際に動作するコードとともに解説する。単なるAPI紹介ではなく、gemとRailsアプリそれぞれの設計パターン、テスト方法、移行戦略まで踏み込んで説明する。

RailsのDeprecation WarningがコンソールにACTIVESUPPORT::DEPRECATION.newで表示されているイメージ


Rails バージョンごとのActiveSupport::Deprecation変遷

Rails 7.0以前、7.1、8.0の3つで大きな変更があった。

Rails 7.0以前(旧パターン)

# Rails 7.0以前のシングルトンパターン(現在は動作しない)
ActiveSupport::Deprecation.warn("'old_method' is deprecated.")
ActiveSupport::Deprecation.behavior = :log

このパターンはRails 7.1で非推奨となり、Rails 8.0で完全に削除された。NoMethodErrorが発生するため、現在のコードベースでは使用できない。

Rails 7.1での変更内容

Rails 7.1(2023年10月リリース)で、ActiveSupport::Deprecationはシングルトンとしての動作を廃止し、インスタンスベースのAPIに移行した。

主な変更点:
ActiveSupport::Deprecation.warn などのクラスメソッドが非推奨化
– 独自のActiveSupport::Deprecationインスタンスを作成して使用する設計に変更
Rails.application.deprecatorsへの登録機能が追加

Rails 8.0での変更内容

Rails 8.0では、7.1で非推奨化されたシングルトン委譲が完全に削除された。7.1時代のActiveSupport::Deprecation.warn(クラスメソッド経由)はRails 8.0ではNoMethodErrorになる。

Rails 7.0・7.1・8.0のDeprecation API変遷の対応図


前提条件

  • Rails 7.1 以上(Rails 8.x を含む)
  • Ruby 3.0 以上推奨
# Railsバージョン確認
bundle exec rails --version
# => Rails 7.1.x または Rails 8.x.x であることを確認

基本実装: 独自Deprecatorインスタンスの作成

Rails 7.1以降の基本パターンは、ActiveSupport::Deprecation.newで独自インスタンスを作成することだ。

# Rails 7.1+ 推奨パターン
# 引数: (廃止予定バージョン, gem/ライブラリ名)
MY_DEPRECATOR = ActiveSupport::Deprecation.new("2.0", "MyLibrary")

# deprecation warningを発する
MY_DEPRECATOR.warn("'old_method' は非推奨です。'new_method' を使ってください。")

コンストラクタの2つの引数は以下の意味を持つ:

引数 説明
第1引数(horizon) この警告を廃止する予定のバージョン "2.0", "3.0"
第2引数(gem_name) ライブラリ・アプリ名 "MyLibrary", "MyApp"

これらはワーニングメッセージに自動的に含まれる:

DEPRECATION WARNING: 'old_method' は非推奨です。'new_method' を使ってください。
(called from ...) Use `MyLibrary.silence { }` or set `MyLibrary.deprecator.behavior = :silence`
to silence these warnings. MyLibrary 2.0 will have deprecated behavior removed.

gemでの実装パターン

gem開発でdeprecation warningを実装する場合、gem固有のDeprecatorを定義する。

モジュール定数として定義する

# lib/my_library.rb
require "active_support/deprecation"

module MyLibrary
  # gemのバージョン管理と連動させる
  DEPRECATOR = ActiveSupport::Deprecation.new("2.0", "MyLibrary")

  def self.deprecator
    DEPRECATOR
  end
end

メソッドにdeprecation warningを付与する

# lib/my_library/old_feature.rb
module MyLibrary
  class OldFeature
    # 旧メソッドを残しつつwarningを出す
    def old_method(options = {})
      MyLibrary::DEPRECATOR.warn(
        "`old_method` は非推奨です。`new_method` を使ってください。" \
        "`options` の渡し方は変わっていません。"
      )
      new_method(options)
    end

    def new_method(options = {})
      # 新しい実装
    end
  end
end

deprecate_kwarg で引数単位の非推奨化

Rails 7.1以降、Module#deprecate_kwargメソッドで特定のキーワード引数だけを非推奨にできる。このメソッドはRailsのActiveSupport内部でも使われているインターフェースで、独自gemでも利用可能だ。

module MyLibrary
  class Configuration
    # :old_option という引数が非推奨で :new_option に置き換えられることを示す
    # シグネチャ: deprecate_kwarg(method_name, old_kwarg, new_kwarg, deprecator)
    deprecate_kwarg :initialize, :old_option, :new_option, MyLibrary::DEPRECATOR

    def initialize(new_option: nil, **options)
      @option = new_option
    end
  end
end

注意: deprecate_kwargはActiveSupportがMixinとして提供するメソッドだ。gemで使用する場合はrequire "active_support/core_ext/module/deprecation"が必要な場合がある。


Railsアプリでの実装パターン

Railsアプリ内でdeprecation warningを使う場合は、Rails.application.deprecatorsに登録することで一元管理できる。

Rails.application.deprecatorsへの登録

# config/initializers/deprecations.rb

# アプリ独自のDeprecatorを登録する
Rails.application.deprecators[:my_feature] = ActiveSupport::Deprecation.new(
  "3.0",
  "MyFeature"
)

Rails.application.deprecators[:legacy_api] = ActiveSupport::Deprecation.new(
  "2.0",
  "LegacyAPI"
)

登録後は以下でアクセスできる:

# アプリのどこからでも参照可能
deprecator = Rails.application.deprecators[:my_feature]
deprecator.warn("このメソッドはv3.0で削除されます。")

登録したDeprecatorを使ってメソッドを非推奨化する

# app/models/user.rb
class User < ApplicationRecord
  def self.find_by_email_old(email)
    Rails.application.deprecators[:legacy_api].warn(
      "`User.find_by_email_old` は非推奨です。" \
      "`User.find_by(email:)` を使ってください。"
    )
    find_by(email: email)
  end
end

Railsの組み込みDeprecatorを使う場合

Rails.application.deprecator(単数形)はRails 7.1で追加されたRailsフレームワーク自身のdeprecation用Deprecatorだ。

# Rails.application.deprecator はRailsフレームワーク自身のDeprecator
# アプリコードやgemのdeprecationではなく、Rails内部の非推奨警告に使われる
Rails.application.deprecator.warn("このコードは非推奨です。")

重要: Rails.application.deprecator(単数形)はRailsコア専用であり、独自機能の非推奨化には使わない。アプリ独自のdeprecationには、Rails.application.deprecators(複数形)に登録した独自インスタンスを使うこと。

{{IMAGE: Deprecatorをinitializerで登録してアプリ全体で使う構成図}}


Deprecationの動作を制御する

ActiveSupport::Deprecationインスタンスのbehaviorを設定することで、警告の出力方法を制御できる。

behaviorの種類

behavior 動作
:raise ActiveSupport::DeprecationExceptionを発生させる
:log Rails.loggerにWARNとして記録する
:notify ActiveSupport::Notificationsでイベントを発行する
:report Rails.errorに報告する(Rails 7.1以降)
:silence 警告を完全に抑制する
:stderr 標準エラー出力に出力する(デフォルト)
# config/initializers/deprecations.rb

deprecator = ActiveSupport::Deprecation.new("2.0", "MyLibrary")

# 環境ごとに動作を変える
if Rails.env.production?
  # 本番はログに記録しつつErrorとして報告
  deprecator.behavior = [:log, :report]
elsif Rails.env.test?
  # テスト環境は例外を発生させて確実に検出
  deprecator.behavior = :raise
else
  # 開発環境はコンソールに表示
  deprecator.behavior = :stderr
end

Rails.application.deprecators[:my_library] = deprecator

silenceメソッドで一時的に抑制する

特定のブロック内だけ警告を無効にしたい場合:

# 特定のブロック内だけdeprecation warningを抑制
Rails.application.deprecators[:my_library].silence do
  # このブロック内のdeprecation warningは出力されない
  old_method_call()
end

全Deprecatorを一括制御する(Rails 7.1+)

# アプリに登録された全DeprecatorのBehaviorを一括設定
Rails.application.deprecators.behavior = :log

# 全Deprecatorを一括でsilence
Rails.application.deprecators.silence do
  # warningが抑制される
end

テストでの検証方法

deprecation warningが正しく発行されるかをテストする方法を解説する。

RSpecでのテスト

# spec/models/user_spec.rb
RSpec.describe User do
  describe ".find_by_email_old" do
    it "deprecation warningを発行する" do
      deprecator = Rails.application.deprecators[:legacy_api]

      # behaviorを:raiseにすることでExceptionとして捕捉できる
      expect {
        deprecator.behavior = :raise
        User.find_by_email_old("test@example.com")
      }.to raise_error(ActiveSupport::DeprecationException)
    end
  end
end

assert_deprecatedを使う(Minitest/Rails標準)

Railsにはassert_deprecatedヘルパーが組み込まれている:

# test/models/user_test.rb
class UserTest < ActiveSupport::TestCase
  test "find_by_email_oldはdeprecation warningを出す" do
    deprecator = Rails.application.deprecators[:legacy_api]

    assert_deprecated(/非推奨/, deprecator) do
      User.find_by_email_old("test@example.com")
    end
  end

  test "warningが出ないことを確認する" do
    deprecator = Rails.application.deprecators[:legacy_api]

    assert_not_deprecated(deprecator) do
      User.find_by(email: "test@example.com")
    end
  end
end

assert_deprecatedの第1引数は正規表現で、警告メッセージのパターンを指定できる。

callstack_message でコール元を特定しやすくする

# callerオフセットを指定してコール元スタックを正確に表示する
def old_method
  MY_DEPRECATOR.warn(
    "'old_method' は非推奨です。'new_method' を使ってください。",
    caller_locations(1, 1)
  )
  new_method
end

deprecation warningの段階的廃止戦略

メジャーバージョンアップ時に非推奨APIを安全に削除するための段階的アプローチ。

Phase 1: 警告の導入(v1.x)

# v1.xで旧メソッドを非推奨化
MY_DEPRECATOR = ActiveSupport::Deprecation.new("2.0", "MyLibrary")

def old_method(arg)
  MY_DEPRECATOR.warn(
    "`old_method(arg)` は v2.0 で削除されます。" \
    "`new_method(value: arg)` に移行してください。"
  )
  new_method(value: arg)
end

Phase 2: CHANGELOGとアップグレードガイドの整備

## Upgrading from v1.x to v2.0

### old_method の削除

`old_method` は v1.x で非推奨化され、v2.0 で削除されました。

**Before (v1.x):**
```ruby
MyLibrary::Client.new.old_method(value)

After (v2.0):

MyLibrary::Client.new.new_method(value: value)

### Phase 3: メジャーバージョンでの削除

```ruby
# v2.0でold_methodを完全削除
# def old_method はもう存在しない

# Deprecatorのhorizonを次のバージョンに更新
MY_DEPRECATOR = ActiveSupport::Deprecation.new("3.0", "MyLibrary")

トラブルシュート

症状 原因 解決策
NoMethodError: undefined method 'warn' for ActiveSupport::Deprecation:Class Rails 8.0以降でシングルトンメソッドが削除された ActiveSupport::Deprecation.newでインスタンスを作成する
deprecation warningが本番で大量にログに出る behavior = :logが本番に設定されている behavior = [:log, :report]で絞り込むかreportのみにする
テストでdeprecation warningを検出できない テスト環境のbehaviorが:silenceになっている behavior = :raiseに変更してExceptionで捕捉する
assert_deprecatedがマッチしない 正規表現パターンが警告メッセージと一致していない deprecator.warnのメッセージと正規表現を見直す
caller_locationsのスタックトレースがずれる offsetの指定が間違っている メソッドの呼び出し深さに応じてoffsetを調整する

まとめ

このガイドでは Rails 7.1/8.x 対応の ActiveSupport::Deprecation 実装について解説した。

  • インスタンスベースのAPI: ActiveSupport::Deprecation.new("version", "name") で独自インスタンスを作成
  • Rails.application.deprecators: Railsアプリでの一元管理に活用
  • behaviorの制御: 環境ごとに:raise:log:reportを使い分ける
  • テスト: assert_deprecated:raisebehaviorでdeprecationを確実に検出
  • 段階的廃止: CHANGELOG整備と移行ガイドを合わせて提供する

Rails 7.0以前のコードベースには古いシングルトンパターン(ActiveSupport::Deprecation.warnのクラスメソッド呼び出し)が残っている場合が多い。Rails 8.0で完全に削除されているため、早急にインスタンスベースのパターンへの移行を推奨する。


関連記事