Railsを5.2.4から6.0.4にアップデートした手順をメモ
Railsを5.2.4→6.0.4にアップデートする機会があった。アップデートのために行った手順、手順ごとの調査をメモした。
手順
1. Rails以外のバージョンが古いライブラリをアップデートする
Railsアップデートの影響範囲を小さくするため、Rails以外のライブラリをアップデートしておく。 特にメジャーバージョンが古いライブラリは上げておく。 全部のバージョンをpatchまで最新にしようとするとキリがないので、そこはラフに判断する。
2. gemfileをrails 6.0.4 にして、bundle update rails を実行し失敗の原因となったgemを最新にする
3. rails を6.0.4 にアップデートする
gem 'rails', '6.0.4'
bundle update rails
して成功することを確認する。
4. rails app:update する
app:updateとは
Rails アップグレードガイド_1.4 アップデートタスク
Gemfileに記載されているRailsのバージョンを更新後、このコマンドを実行することで、新しいバージョンでのファイル作成や既存ファイルの変更を対話形式で行うことができます。
app:updateを実行する
m(migrate)を打ってエディタで差分を確認したが、今回は修正すべき内容が特に見当たらなかった。
5. RailsガイドのRails 5.2からRails 6.0へのアップグレードを読む
Rails 5.2からRails 6.0へのアップグレード 以下、特に気になった項目を列挙。
npmの全パッケージが@railsスコープに移動
これまで「actioncable」「activestorage」「rails-ujs」パッケージのいずれかをnpmまたはyarn経由で読み込んでいた場合は、これらを6.0.0にアップグレードする前にそれらの依存関係の名前を以下のように更新しなければなりません。
rails-ujsであれば rails-ujsから@rails/ujsへ変更する
Rails 6のアップデートに先立ってこちらを修正してリリースした。
セキュリティ向上のためpurposeとexpiryメタデータが署名済みおよび暗号化済みCookieに埋め込まれるようになった
cookieを引き続きRails 5.2以前でも読み取れるようにする必要がある場合や、6.0のデプロイを検証中で前のバージョンに戻せるようにしたい場合は、Rails.application.config.action_dispatch.use_cookies_with_metadataにfalseを設定してください。
Rails 6.0から5.2へ巻き戻せるようにしたいので設定する。
purposeについては以下を参考にした。
Rails 6のcookieに「purpose」メタデータが追加(翻訳)|TechRacho by BPS株式会社
autoloaderがclassicからzeitwerkに変更される
アップデートの影響を小さくするため、ひとまずclassicのままRails 6.0へアップデートすることにした。
Rails アップグレードガイド_3.7.15 Rails 6でclassicモードのオートローダーを使う方法
6. 動かして出てきた不具合を修正する
rails sしてログを見たり、自動テストの結果を見たり、アプリケーションを触って出てきた不具合を修正していく。
a. hamlit-rails 周りのDEPRECATION WARNING
W, [2021-09-09T10:57:35.117155 #87570] WARN -- : DEPRECATION WARNING: Single arity template handlers are deprecated. Template handlers must now accept two parameters, the view object and the source for the view object. Change: >> #<Hamlit::RailsTemplate:0x00007fef2f58f2c0>.call(template) To: >> #<Hamlit::RailsTemplate:0x00007fef2f58f2c0>.call(template, source)
参考: Rails 6 を動かす際に "DEPRECATION WARNING: Single arity template handlers are deprecated." という警告が出た場合の対処
bundle update hamlit-rails
を実行し
HamlをアップデートしたらDEPRECATION WARNINGは消えた。
こちらはrails6.0アップデートに先立ってリリースした。
b. RailsのInitialize中にautoloadを使ったクラスの読み込みで警告が出る
ログ
W, [2021-09-09T11:11:44.347319 #88314] WARN -- : DEPRECATION WARNING: Initialization autoloaded the constant FooBar. Being able to do this is deprecated. Autoloading during initialization is going to be an error condition in future versions of Rails. Reloading does not reboot the application, and therefore code executed during initialization does not run again. So, if you reload FooBar, for example, the expected changes won't be reflected in that stale Class object. `config.autoloader` is set to `classic`. This autoloaded constant would have been unloaded if `config.autoloader` had been set to `:zeitwerk`. Please, check the "Autoloading and Reloading Constants" guide for solutions.
原因
RailsのInitialize中はautoloadが有効でないため。
initialiizers実行時はZeitwerkのオートロードが有効になっていません。 Railsは一連のinitializerを実行した最後に、Rails::Application::Finisherを呼び出しますが、ここで初めてZeitwerkが起動する仕組みになっています。
解決策
クラス読み込みが失敗したファイルにrequireを追加すれば解決する。
require './path/to/fobar'
あるいは以下の記事にあるように config.after_initialize
を使ってもうまくいく。
STORES Rails アプリを Zeitwerk 有効化するまでの道のり - STORES Tech Blog
ログにある通り、initializerで何らかのクラスを変数にセットしている場合、それらはautoloadしても更新されない(stale Class objectになる) 点に注意する
Reloading does not reboot the application, and therefore code executed during initialization does not run again. So, if you reload FooBar, for example, the expected changes won't be reflected in that stale Class object.
参考: 定数の自動読み込みと再読み込み (Zeitwerk)_6.1 古くなったオブジェクトの再読み込み
c. Rails 6.0から入ったenum negative scope がアプリケーションで定義したscopeをoverrideしている
W, [2021-09-09T12:20:28.272948 #90420] WARN -- : Creating scope :not_deleted. Overwriting existing method FooBar.not_deleted.
ネガティブスコープの影響 https://railsguides.jp/6_0_release_notes.html
すべてのenum値についてネガティブスコープを追加 (Pull Request) https://github.com/rails/rails/pull/35381
動作検証したところ、アプリケーションで定義したFooBar.not_deletedはoverrideされ、enum negative scopeが呼ばれていた。 今回のケースでは、どちらのスコープでも実行されるsqlが全く一緒だったので、アプリケーションで定義したFooBar.not_deletedを削除することで解決した。
d.config_forから返されるハッシュのsliceメソッドの引数にシンボルでなく文字列でアクセスするとエラー
configuration.foo_barのkeyが5.2系ではstringだったのに6.0系ではsymbolになっている。
https://railsguides.jp/6_0_release_notes.html#railties-%E9%9D%9E%E6%8E%A8%E5%A5%A8%E5%8C%96
config_forから返されるハッシュにシンボルでないキーでアクセスすることを非推奨化 (Pull Request)
Hash.[]
ならstring keyでもアクセスできるが、Hash.sliceだと非推奨化じゃなくて取得できなかった
6.0のログ
Loading development environment (Rails 6.0.4) [1] pry(main)> Rails.configuration.foo_bar => {:a=>[1, 2], :b=>[3], :c=>[4, 5, 6, 7]} [2] pry(main)> Rails.configuration.foo_bar.slice('a','b').values.flatten.sort => [] [3] pry(main)> Rails.configuration.foo_bar.slice('a','b') => {} [4] pry(main)> Rails.configuration.foo_bar.slice(:a,:b) => {:a=>[1, 2], :b=>[3]} [1] pry(main)> Rails.configuration.foo_bar['a'] # [] のアクセスなら取得できる => [1, 2] [2] pry(main)> Rails.configuration.foo_bar.slice('a') # slice だと取得できない => {} [3] pry(main)> Rails.configuration.foo_bar.class => ActiveSupport::OrderedOptions # https://api.rubyonrails.org/v6.0.4/classes/ActiveSupport/OrderedOptions.html string key を symbol に変換する処理あり # ['string key']['string key'] とすると DEPRECATION WARNINGが出た [8] pry(main)> Rails.configuration.repro['a']['b'] DEPRECATION WARNING: Accessing hashes returned from config_for by non-symbol keys is deprecated and will be removed in Rails 6.1. Use symbols for access instead. (called from <main> at (pry):8) W, [3-09-10T12:26:05.948315 #5023] WARN -- : DEPRECATION WARNING: Accessing hashes returned from config_for by non-symbol keys is deprecated and will be removed in Rails 6.1. Use symbols for access instead. (called from <main> at (pry):8)
5.2のログ
D, [3-09-10T11:01:22.410693 #3695] DEBUG -- : (8.2ms) SET NAMES utf8mb4 COLLATE utf8mb4_general_ci, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 Loading development environment (Rails 5.2.6) [1] pry(main)> Rails.configuration.foo_bar => {"a"=>[1, 2], "b"=>[3], "c"=>[4, 5, 6, 7]} [3] pry(main)> Rails.configuration.foo_bar.slice('a','b').values.flatten.sort => [3, 1, 2] [2] pry(main)> Rails.application.config_for(:foo_bar).class => Hash # 6.0.4だとActiveSupport::OrderedOptionsに変わっている
解決策
sliceしている箇所はstring_keyからsymbol_keyにした。 他、怪しそうな箇所がちゃんと動いているか調べた。
d. ActiveRecordインスタンスの配列のキャッシュのデシリアライズに失敗する
Rails.cache.fetch('some_key') do FooBar.scope_method.to_a end
のようにActiveRecordインスタンスの配列をシリアライズして保存していた。 これのでシリアライズに失敗するようになった。
原因
Rails 5 -> 6でシリアライズの形式が変わったためだと思われる。
解決策
cacheのkeyの末尾にバージョンを入れ、Rails 6系の時にRails 5系のキャッシュへのヒットを避けた。
Rails.cache.fetch("some_key_#{Rails.configuration.cache.version}") do FooBar.scope_method.to_a end
採用しなかった解決策
- versionオプションを使う
- https://api.rubyonrails.org/v5.2.5/classes/ActiveSupport/Cache/Store.html#method-i-fetch
Setting :version verifies the cache stored under name is of the same version. nil is returned on mismatches despite contents. This feature is used to support recyclable cache keys.
- versionが存在しない場合は別バージョンと認識されなかったため
- 5.2.4の時点でバージョンがない → 6.0.4でバージョン指定すると、別バージョンと認識されず5.2.4のキャッシュを取り出してしまう。
- 今回はversionオプションを使わなかったが、メンテを継続するにあたってはversionオプションを利用するのが良いと思う。
感想
自動テストを書いておくことがとても重要。上記に挙げた不具合の一部は、自動テストのおかげで発見できた。
自動テストによる検証が大切なことは、以下にも記述されている。 Rails アップグレードガイド - Railsガイド
アップグレード後にアプリケーションが正常に動作していることを確認する方法としては、良いテストカバレッジをアップグレード前に準備しておくのが最善です。アプリケーションを一気に検査する自動テストがないと、変更点をすべて手動で確認しなければならず膨大な時間がかかってしまいます。Railsのようなアプリケーションの場合、これはアプリケーションのあらゆる機能を一つ残らず確認しなければならないということです。アップグレードの実施は、テストカバレッジをきちんと準備してから行なうよう、くれぐれもお願いします。