Railsを8.0にアップグレードした際の手順や知見をまとめる
はじめに
この記事では、私が Rails 7.2.2.1から最新の8系にアップグレードした際の手順や、遭遇した問題などについてまとめてみました。
初めてアップグレードを行う方を意識しているので、実際に行った手順を網羅的に書いています。(ですので、「8系へのアップグレード」に限らず、全般的に言えることも色々書いています)
これから Rails のアップグレードを行う方の参考になれば幸いです。
事前準備
アップグレードガイドの確認
まずは Rails ガイド記載のアップグレードガイドに目を通します。
-
Ruby のバージョン互換性
- アップグレードしたい Rails のバージョンが、現在使用している Ruby のバージョンと互換性があるかを確認
- Ruby 3.2.0 以上あることを確認
-
アップグレード手順
- マイナーバージョンを 1 つずつ上げていくことや、
bundle update rails
後にアップデートタスクを実行するなど、基本的な方針を確認
- マイナーバージョンを 1 つずつ上げていくことや、
-
Rails 8における破壊的変更の確認
- リリースノートをみて破壊的変更がないかを確認
Deprecation Warning への対応
先述のリリースノートでも記載があった、廃止予定の設定・機能は Deprecation Warning が出てくれます。
我々の環境では以下のような警告が出ていました。
- キーワード引数を使った enum 定義
DEPRECATION WARNING: Defining enums with keyword arguments is deprecated and will be removed
in Rails 8.0. Positional arguments should be used instead:
enum :kind, {:hoge=>0, :foo=>1}
Rails 8.0 からキーワード引数を使ったenum
の定義方法が廃止され、位置引数を使った方法に移行する必要があります
enum kind: { hoge: 0, foo: 1 }
↓
enum :kind, { hoge: 0, foo: 1 }
- config.read_encrypted_secrets
==DEPRECATION WARNING==: 'config.read_encrypted_secrets=' is deprecated and will be removed in Rails 8.0.
詳細はRails ガイド記載の通りです。廃止されるため削除しました。
もっと早い段階でこういった Warning は処理できているといいですね。
Gem の互換性確認
我々のプロジェクトでは Renovate を使用して定期的に Gem のアップデートを行なっています。しかし、Gem が最新だからといって Rails 8 系に対応しているかは分かりません。
そこで使ってみたのが RailsBump です。
プロジェクトの Gemfile.lock を貼り付けるとその Gem が Rails 8 系に対応しているか確認できます。
「checking」ステータスになっている Gem に関しては、GitHub の Issue や Pull Request を確認し、Rails 8 系との互換性について明記されているか、関連する問題が起きていないかなどをチェックします。
アップグレード作業
準備が整ったら、いよいよアップグレード作業を開始します。
Gemfile の編集
Gemfile
内の Rails のバージョン番号を変更します。
gem 'rails', '~> 8.0.0'
その後、bundle update rails
を実行し、Rails と関連 Gem をアップデートします。
rails app:update の実行
rails app:update
を実行します。これにより、config配下のファイル等が新しい Rails の設定に合わせて更新されます。既存の設定を考慮しながら、新しい設定を適用していきます。
こんな感じで各ファイルごとにインタラクティブに更新できます。
Overwrite /project/config/application.rb? (enter "h" for help) [Ynaqdhm] y
force config/application.rb
identical config/environment.rb
conflict config/puma.rb
その後
-
テストの実行
- 一通りのアップグレード作業が終わったら、必ずテストを実行してアプリケーションが正常に動作するかを確認します。
-
RailsDiff の確認
- 念の為、RailsDiff も使ってバージョン間の設定ファイルの差異も確認してみました。ただ、
rails app:update
で更新したもの以外で新しい差分はありませんでした。
- 念の為、RailsDiff も使ってバージョン間の設定ファイルの差異も確認してみました。ただ、
アップグレード後に発生したエラー
bundle update rails
の後にテストを実行したところ、以下の 2 つが影響してエラーが発生しました。
開発環境とテスト環境における routes の遅延読み込み
Failure/Error: ss = Sidekiq::ScheduledSet.new
NameError:
uninitialized constant Sidekiq::ScheduledSet
Sidekiq::ScheduledSet
を利用するには、本来 require 'sidekiq/api'
が必要です。以前までは config/routes.rb
で以下のよう記述することで、間接的に sidekiq/api
が読み込まれていました。
mount Sidekiq::Web, at: '/sidekiq
しかし、Rails 8 から開発環境とテスト環境において、デフォルトで routes が遅延読み込みされるようになりました。この影響で、テスト実行時に sidekiq/api
が自動で読み込まれなくなったというわけです。
参考: Rails 8's Lazy Route Loading & Devise
Sidekiq::ScheduledSet
を呼び出している箇所で明示的に require 'sidekiq/api'
を追加することで解決しました。
Rails 8 に対応していない Gem
NoMethodError:
undefined method `arel_table' for nil:NilClass
active_record_union
という Gem の影響で上記のエラーを吐くようになりました。調査した結果、active_record_union
の最新バージョンが、アップグレード実施時点で、Rails 8 系に対応していないことが判明しました、、、
master ブランチに修正がマージされていそうですが、それがリリースされていない状態のようです。
RailsBump でも checking status だったため GitHub を確認していたのですが、こちらのIssueを見落としていました、、、、
(コミットメッセージに 6.0 → 8.0 と書いてあったので油断していました、、、)
active_record_union
を使用している箇所が幸運にも削除できたので、Gemfile からも削除しました。
RailsBump を確認しても見落としはあるので、やはりテストは重要です。
config.load_defaults を 8.0 にする
上記エラーを解消しテストが通ったら、一旦ここまででリリースを行いました。
リリース後、config/initializers/new_framework_defaults_8_0.rb
でコメントアウトされている新しいデフォルト設定を順次有効化していき、最終的にapplication.rb
のconfig.load_defaults
を 8.0 に更新します。
8 系からの適用される新しいデフォルト値は以下です
Regexp.timeout = 1
Rails.application.config.action_dispatch.strict_freshness = true
Rails.application.config.active_support.to_time_preserves_timezone = :zone
参考: Configuring Rails Applications - Rails Guides
Regexp.timeout = 1
正規表現のパターン処理にタイムアウトを設定するものです。
Regexp.timeout=
は Ruby 3.2 から利用出来るようになったセキュリティ強化策の一つで、Rails 8 から 1 がデフォルトで設定されるようになります。正規表現のパターン処理が 1 秒を超えた場合にRegexp::TimeoutError
を発生させます。
これにより ReDos 攻撃を防止することができます。
$ Regexp.timeout = 0.00001
=> 1.0e-05
$ Regexp.timeout
=> 1.0e-05
$ /([a-z]+)*$/ =~ "a" * 1000 + "!"
=> regexp match timeout (Regexp::TimeoutError)
念の為、アプリケーション内での正規表現チェックにどのくらい時間がかかっているかをログとして出力して検証してみました。大体マイクロ秒レベルで処理できていたのでTimeoutErrorが頻発するといった問題は起きないだろうと判断できました。
Rails.application.config.action_dispatch.strict_freshness = true
HTTP ヘッダーに If-Modified-Since
と If-None-Match
の両方が存在していた場合の挙動に関する設定です。
これらはキャッシュを使うかどうかの制御に使われるヘッダーですね。
- false
- 両方のヘッダーをチェック
-
If-Modified-Since
とLast-Modified
/If-None-Match
とETag
を比較
-
- 両方が一致した場合のみ304を返す
- 両方のヘッダーをチェック
- true
-
If-None-Match
だけチェック
-
entity tags are presumed to be more accurate than date validators.
Rails.application.config.active_support.to_time_preserves_timezone = :zone
to_timeメソッドの挙動に関わる設定です。
-
:zone
- レシーバーのタイムゾーン情報を保持します
-
:offset
- レシーバーのオフセット情報を保持します。
Rails 8 からは :zone
がデフォルトになります。
夏時間が存在するタイムゾーンで挙動に違いが出るようです。
以下でタイムゾーンをAmerica/New_York
にして :offset
と :zone
の挙動の違いを確認してみます。
America/New_York
は東部標準時(EST)と東部夏時間(EDT)という2つに分けられます。EST の UTC オフセットは-5 時間で EDT の場合-4 時間です。EDTは3月の第2日曜日午前2時に始まり、11月の第1日曜日午前2時に終了します。
:offset
$ Rails.application.config.active_support.to_time_preserves_timezone
=> :offset
$ Time.zone = "America/New_York"
=> "America/New_York"
$ now = Time.zone.parse('2024-08-05T05:00:00').to_time
=> 2024-08-05 05:00:00 -0400
$ now.zone
=> nil
$ now + 6.months
=> 2025-02-05 05:00:00 -0400
$ (now + 6.months).utc
=> 2025-02-05 09:00:00 UTC
now + 6.months
をした結果の 2025-02-05 05:00:00
は EST のため UTC オフセットは-0500
であるべきです。しかし、now はタイムゾーン情報を持たないため、オフセットが-0400
を引き継いでしまっています。
:zone
$ Rails.application.config.active_support.to_time_preserves_timezone
=> :zone
$ Time.zone = "America/New_York"
=> "America/New_York"
$ now = Time.zone.parse('2024-08-05T05:00:00').to_time
=> 2024-08-05 05:00:00 -0400
$ now.zone
=> #<ActiveSupport::TimeZone:0x0000000106adeb00 @name="America/New_York", @tzinfo=#<TZInfo::DataTimezone: America/New_York>, @utc_offset=nil>
$ now + 6.months
=> 2025-02-05 05:00:00 -0500
$ (now + 6.months).utc
=> 2025-02-05 10:00:00 UTC
保持してあるタイムゾーン情報により、2025-02-05 05:00:00
時点では EST が適用され、オフセットが-0500
になっています。
まとめ
設定をよく確認しながら適用していき、段階的にリリースするなどしたおかげで本番環境で問題は起きませんでした。
当たり前ですが事前準備が大事です。Deprecation Warningに早めに対処しておくと後々楽になります。RailsBumpやAIといったツールを使うのもいいですが、やはりテストがしっかりしていないと安心できません。
今回はAIを調査目的でのみ使用しましたが、次回はより積極的に活用して、その効果を検証してみたいと考えています。
Discussion