弊社はRuby on Railsを中心に扱っている、Webサービス受託開発会社です。
先日、運用中のサービスを Rails 6.0 にアップグレードしました。
私は長くRailsと関わっているのでほぼルーティンになりましたが、誰かの参考になるかもしれないので手順を書き留めておきます。
アップグレード対象によって、追加で作業が必要になるかもしれません。
場合によってはgemの依存関係を解決できずforkしたり、他のgemに置き換えたりすることもあるでしょう。
作業方針の参考程度に捉えていただければと思います。
(といっても、ほとんど Railsガイドのアップグレードガイド にある通りに作業しただけで、ちょっとした補足をしているだけです)
日頃からできているとよいこと
- バージョン管理システムの利用
- 自動テストの整備
- gemのアップデート
- なるべく新しいバージョンのRubyへの対応
バージョン管理システムの利用
言うまでもなくアップグレードは一筋縄ではいかないことも多いので、少しずつ作業したり、差し戻したり、作業中にほかの作業を取り込んだり・・・といったことも発生します。
また、アップデートタスクでフレームワークのファイルを更新したとき、差分を確認するのに便利です。
自動テストの整備
Railsのアップグレードは大きな変更を伴うものですから、まず壊れます。これを見つけ、正すには自動テストの整備は必須です。
gemのアップデート
rails gem のアップデートにあわせて、Railsに依存する gem のアップデートが必要になる場合は多いです。
依存gem のアップデートによる影響が最小限になるよう、gemはなるべく最新に保つようにします。
なお、弊社では原則として Gemfile
でバージョンを縛らない運用をしています。
なるべく新しいバージョンのRubyへの対応
Railsはリリース時点の最新のRubyをサポートします。
gemと同様に、Rubyのバージョンも追従したほうがよいです。
アップグレードの実施
0. DEPRECATION WARNING 潰し
Rails は将来互換性がなくなる機能を利用しているとき、警告と移行方法を提示してくれます。
アップグレード前に壊れることがわかるので、アップグレードの前に修正しておきます。
1. gemのアップデート
各gemを可能な限りアップデートして、正しく動作することを確認しておきます。
2. Rubyのアップデート
アップデート前、アップデート後両方が動くRubyバージョンのうち、最新のものにアップデートして正しく動作することを確認しておきます。
今回は Ruby 2.6 にしました。
3. rails gem のアップデート
Rails アップグレードガイドに従って作業を進めていきます。
# before: gem 'rails', '~> 5.2'
gem 'rails', '~> 6.0'
$ bundle update rails
rails gemのバージョンが変化することで解決できなくなる依存関係が列挙されるので、それぞれ確認ます。
Fetching gem metadata from http://rubygems.org/.........
Fetching gem metadata from http://rubygems.org/.
Resolving dependencies.....
Bundler could not find compatible versions for gem "railties":
In Gemfile:
devise was resolved to 4.7.0, which depends on
railties (>= 4.1.0)
jquery-rails was resolved to 4.3.5, which depends on
railties (>= 4.2.0)
letter_opener_web was resolved to 1.3.4, which depends on
railties (>= 3.2)
meta_request was resolved to 0.7.2, which depends on
railties (>= 3.0.0, < 7)
rails (~> 6.0) was resolved to 6.0.0, which depends on
railties (= 6.0.0)
rails-i18n was resolved to 5.1.3, which depends on
railties (>= 5.0, < 6)
rspec-rails was resolved to 3.8.2, which depends on
railties (>= 3.0)
sassc-rails was resolved to 2.1.2, which depends on
railties (>= 4.0.0)
web-console was resolved to 3.7.0, which depends on
railties (>= 5.0)
rails 6.0 が railties 6.0.0 を要求しているものの、
rails-i18n 5.1.3 が railties 6 未満を要求していることがわかります。
ここで依存性の重複が発生したようです。
Bundler: bundle update - Overlapping Dependencies
rails-i18n を確認してみます。
https://rubygems.org/gems/rails-i18n
すると、rails-i18n 6.0.0 という、 rails 6.0 に対応したものがリリースされていました。
https://rubygems.org/gems/rails-i18n/versions/6.0.0
rails と一緒に rails-i18n をアップデートしてみます。
$ bundle update rails railties rails-i18n
無事 rails gem のアップデートができました。
Fetching gem metadata from http://rubygems.org/.........
Fetching gem metadata from http://rubygems.org/.
Resolving dependencies.....
(snip)
Using activesupport 6.0.0 (was 5.2.3)
(snip)
Using activemodel 6.0.0 (was 5.2.3)
(snip)
Using activejob 6.0.0 (was 5.2.3)
Using activerecord 6.0.0 (was 5.2.3)
(snip)
Using actionview 6.0.0 (was 5.2.3)
(snip)
Using actionpack 6.0.0 (was 5.2.3)
(snip)
Using activestorage 6.0.0 (was 5.2.3)
Using actionmailer 6.0.0 (was 5.2.3)
(snip)
Using actioncable 6.0.0 (was 5.2.3)
(snip)
Using railties 6.0.0 (was 5.2.3)
(snip)
Using rails 6.0.0 (was 5.2.3)
Using rails-i18n 6.0.0 (was 5.1.3)
(snip)
Bundle updated!
4. アップデートタスク
アップデートタスクを実行します。
$ bundle exec rails app:update
途中 conflict が報告され、上書きしてよいか確認してくれます。
適切にバージョン管理されていれば上書き後差分を確認できますし、戻すこともできるので、すべて上書きします。
conflict config/boot.rb
Overwrite /home/takeyuweb/Projects/myapp/config/boot.rb? (enter "h" for help) [Ynaqdhm] a
force config/boot.rb
exist config
conflict config/routes.rb
force config/routes.rb
conflict config/application.rb
force config/application.rb
identical config/environment.rb
conflict config/cable.yml
force config/cable.yml
conflict config/puma.rb
force config/puma.rb
identical config/storage.yml
exist config/environments
conflict config/environments/development.rb
force config/environments/development.rb
conflict config/environments/production.rb
force config/environments/production.rb
conflict config/environments/test.rb
force config/environments/test.rb
exist config/initializers
identical config/initializers/application_controller_renderer.rb
conflict config/initializers/assets.rb
force config/initializers/assets.rb
identical config/initializers/backtrace_silencers.rb
conflict config/initializers/content_security_policy.rb
force config/initializers/content_security_policy.rb
identical config/initializers/cookies_serializer.rb
create config/initializers/cors.rb
identical config/initializers/filter_parameter_logging.rb
identical config/initializers/inflections.rb
conflict config/initializers/mime_types.rb
force config/initializers/mime_types.rb
create config/initializers/new_framework_defaults_6_0.rb
identical config/initializers/wrap_parameters.rb
exist config/locales
conflict config/locales/en.yml
force config/locales/en.yml
remove config/initializers/cors.rb
exist bin
identical bin/rails
identical bin/rake
conflict bin/setup
force bin/setup
identical bin/yarn
rails active_storage:update
Copied migration 20190829054009_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb from active_storage
After this, check Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app.
更新のあったファイルを個別に確認して、必要に応じて編集したり、差し戻したりしていきます。
たとえば routes.rb を上書きした場合は、ルーティングの記述を復活させる必要があるでしょう。
差分を確認しながら進めていきます。
config.load_defaults について
アップグレード時はなるべく変更が小さくなるように努めるべきだと考えます。
まずは従来のバージョンのデフォルト設定を使うようにします。
config.load_defaults 5.2
Rails 5.2 => 6.0 固有の内容について
Rail アップグレードガイド - Rails 5.2からRails 6.0へのアップグレード
を参考に作業を進めます。
今回はCookieの互換性がいますぐ失われるのを避けたかったので、以下を new_framework_defaults_6_0.rb
で設定しました。
Rails.application.config.action_dispatch.use_cookies_with_metadata = false
また、Action Cable を使用していたのでそちらの対応を行いました。
オートローダーについて
Rails 6.0 からはオートローダーが Zeitwerk になり、オートロードの挙動が変わっています。
config.load_defaults 5.2 では従来のものが使われるので、いったん置いておきます。
従来のオートローダーで正しく動くことを保証できるようになってから、切り替えることを考えることにします。
テスト実行
$ bundle exec rails test
# または
$ bundle exec rspec
おそらく大量のテストが失敗するでしょう。
地道に修正していきます。(大抵は変な書き方をしてたり、隠れてたバグが表面化したり、といったケーススだったりします)
rspec-rails を使っている場合
Rails 6.0 対応の rspec-rails は4系になりますが、この記事の作成時点でベータ版( 4.0.0.beta2 )のため、 bundle update するだけでは入りません。Gemfile
を触ります。
https://rubygems.org/gems/rspec-rails
gem 'rspec-rails', '4.0.0.beta2'
$ bundle update rspec-rails
今後やっていくこと
DEPRECATION WARNING 潰し
Rails 6.1 で互換性がなくなる機能を使っていると警告が表示されるので修正していきます。
DEPRECATION WARNING: Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1. To continue case sensitive comparison on the :beacon attribute in User model, pass
case_sensitive: true
option explicitly to the uniqueness validator.
config.load_defaults 6.0 にする
今回 new_framework_defaults_6_0.rb
に Rails.application.config.action_dispatch.use_cookies_with_metadata = false
を設定しました。
検討や必要に応じて修正を進め、 new_framework_defaults_6_0.rb
を削除できるようになれば、晴れて config.load_defaults 6.0
にでき、Rails 6.0 対応を終えたといえるでしょう。
Zeitwerk に対応させる
config.load_defaults 6.0 にした後も、当初は従来と同じオートロードの挙動になるようにするには、以下のようにします。
config.autoloader = :classic
ドキュメントに従って、従来のオートローダーから新しいオートローダーである Zeitwerk に対応するための修正を行っていきます。
Rails 5.2からRails 6.0へのアップグレード オートローディング
終わりに
Railsに限らず、アップグレードのコツは、溜めないことだと思っています。
個人的には、Rails 5.2 から 6.0 へは、メジャーバージョンアップにしては楽だと感じました。
皆さんも、サックリとアップグレードを済ませて、6.1 の登場を待ちましょう。