永久保存版！？伊藤さん式・Railsアプリのアップグレード手順

## はじめに

Railsアプリケーションを長く運用していると避けて通れないのがRailsのバージョンアップです。

古いバージョンのRailsは順次サポートの対象から外れていく（＝不具合修正やセキュリティ対応がされなくなる）ため、バージョンアップをせずに運用するわけにはいきません。

そこでこの記事では僕・伊藤淳一がRailsアプリのバージョンをアップグレード（アップデート）する手順を紹介します。

この手順はこれまで何度もRailsアプリケーションをアップグレードしてきた僕の知見が詰まった、いわば「秘伝のタレ」的なアップグレード手順です。

## 想定するRailsアプリケーション

この記事で想定しているのは以下のようなRailsアプリケーションです。

- 開発者1人でもなんとか面倒が見れるレベルの規模（＝アップグレードは1人で作業する想定）
- 趣味で作っているのではなく、外部のユーザーがいるRailsアプリ（＝不具合が発生すると困る人が出てくるサービス）

また、この記事でいうアップグレードとは、マイナーアップグレード（例：5.1系から5.2系）とメジャーアップグレード（例：5.2系から6.0系）のことです。

## 動画版もあります！（2019.10.25追記）
「[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)」のサンプルアプリケーションをRails 5.1.1からRails 6.0.0にアップグレードする様子を動画に撮ってYouTubeにアップしています。

前編、後編とも30分あるので少し長いですが、実践的な内容になっているのでこちらも参考にしてみてください。

- <a href="https://youtube.com/watch?v=hT68fhuWbHU">【前編】永久保存版！？伊藤さん式・Railsアプリのアップグレード手順</a>
- <a href="https://youtube.com/watch?v=SnwNFMauzjM">【後編】永久保存版！？伊藤さん式・Railsアプリのアップグレード手順</a>

また、上の動画に関連するこちらのブログ記事もあわせてどうぞ。

- [【動画付き】Everyday RailsのサンプルアプリをRails 6で動かす際に必要なテストコードの変更点 \- give IT a try](https://blog.jnito.com/entry/2019/10/25/053521)

## 手順の概要

この記事は長いので、先に手順の概要を示しておきます。

- Rails以外のgemを最新にする
- Rubyのバージョンを最新にする
- Railsをバージョンアップする
- `rails app:update`タスクを実行する
- `rails c`や`rails s`でRailsが正常に起動することを確認する
- 自動テストと手動テストで動作確認する
- 問題が無さそうならサーバーにデプロイする

ざっくり言うとこんな感じになります。
それでは以下が本文です。

## 1. 公式のアップグレードガイドに目を通す

Railsガイドにはアップグレードの方法を説明している項があります。
アップグレードする際の主な注意点もバージョンごとに載っているので、まずはこのページに目を通します。

[Rails アップグレードガイド \- Rails ガイド](https://railsguides.jp/upgrading_ruby_on_rails.html)

日本語訳が追いついていないケースもあるので、英語版にも目を通しておく方がよいでしょう。

[Upgrading Ruby on Rails — Ruby on Rails Guides](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html)

## 2. テストが全部パスすることを確認する

実際のアップグレード作業を開始する前に、RSpecやMinitestで書いたテストコードを実行し、全部テストがパスすることを確認します。

```
RUBYOPT=-W:deprecated bundle exec rspec
```

（2022.10.2追記）
Ruby 2.7.2から `RUBYOPT=-W:deprecated` オプションを付けないと、Ruby関連の非推奨警告が出力されなくなったので、上のコマンドにも `RUBYOPT=-W:deprecated` を追加しています。
もし警告が出ていたらメッセージの内容を読み、警告が出なくなるように対処してください。

### カバレッジにも注目し、必要に応じてテストを書き足す

テストが全部パスしていても、カバレッジが低いとテスト不足のため、アップグレード版をリリースした後にシステムエラーが発生、ということが起きかねません。

[Simplecov](https://github.com/colszowka/simplecov)のようなgemを使って、どのファイルがどれくらいテストされているか確認しておきましょう。

そして、「このロジックをテストコードでテストしていないのは怖い」という箇所が見つかったら、アップグレードする前にテストを書いておきましょう。

テストコードを書くのは面倒かもしれませんが、このあとのステップで何度もテストを実行します。
急がば回れの精神でテストを書きましょう。
トータルで見ればその工数は必ずペイできるはずです。

## 3. 開発用ブランチを作成する

いつでもバージョンアップ前のコードベースに戻せるよう、開発用ブランチを作成しておきます。

```console
# 例：Rails 6に移行するための開発用ブランチを作成する
git checkout -b rails-6-0-migration
```

## 4. Rails以外のgemをバージョンアップする

Rails本体のバージョンを先に上げてしまうと、DeviseやCarrierwaveのような周辺のgemが最新のRailsに対応しておらず、思いがけないエラーが起きるかもしれません。

そもそも、古いgemが大量に混じっていると、`bundle update rails`を実行したときにバージョン番号の解決がうまくいかず、`bundle update`が正常に完了できない、ということもよく起きます。

なので、先にRails以外のgemを可能な限り最新版にバージョンアップしていきます。
具体的な手順は以下のとおりです。

### 4-a. 最新ではないgemを探す

`bundle outdated`というコマンドを使うと、Railsアプリ内で最新バージョンを使っていないgemの一覧が得られます。
さらに、[bundle_outdated_formatter](https://github.com/emsk/bundle_outdated_formatter)というgemを使うと、`bundle outdated`の結果を見やすく整形することができます。

参考：[bundle outdatedコマンドの出力を汎用的な形式に変換するgemを作りました \- Qiita](https://qiita.com/emsk/items/e6c2ab26ebcada991248)

```console 
# bundle_outdated_formatterの出力例
bundle outdated | bof --format markdown

| gem | newest | installed | requested | groups |
| --- | --- | --- | --- | --- |
| bootstrap-sass | 3.4.1 | 3.4.0 | | default |
| capybara | 3.28.0 | 3.12.0 | | test |
| childprocess | 2.0.0 | 0.9.0 | | |
| chromedriver-helper | 2.1.1 | 2.1.0 | | test |
| coffee-rails | 5.0.0 | 4.2.2 | | default |
| dotenv | 2.7.5 | 2.5.0 | | |
| dotenv-rails | 2.7.5 | 2.5.0 | | development, test |
```

上の出力例をマークダウンのテーブルとして表示した場合↓

| gem | newest | installed | requested | groups |
| --- | --- | --- | --- | --- |
| bootstrap-sass | 3.4.1 | 3.4.0 | | default |
| capybara | 3.28.0 | 3.12.0 | | test |
| childprocess | 2.0.0 | 0.9.0 | | |
| chromedriver-helper | 2.1.1 | 2.1.0 | | test |
| coffee-rails | 5.0.0 | 4.2.2 | | default |
| dotenv | 2.7.5 | 2.5.0 | | |
| dotenv-rails | 2.7.5 | 2.5.0 | | development, test |

この結果を見ながら、どのgemがどれくらい最新バージョンから古くなっているのかを分析します。


**2022.9.3追記**
最近のBundlerは`bundle outdated`だけである程度見やすい形で出力されるようになりました。
逆にbundle_outdated_formatterはちゃんと動かなくなったので、`bundle outdated`だけでOKです。

### コラム：原則としてGemfileにバージョンは指定しない

特別な理由がない限り、Gemfileにはgemのバージョンを指定しないようにします。

```ruby:Gemfile
# NG: バージョンを指定する
gem 'devise', '~> 4.5.0'

# OK: バージョンを指定しない
gem 'devise'
```

バージョンを指定しない方が、このあとで全部のgemを一気に最新版にアップデートしやすくなるためです。

バージョンを指定する明確な理由がある場合はその理由をコメントとして書いておきます。
以下はその記述例です。

```ruby:Gemfile
# 4.6以上にするとxxxでエラーが発生する。以下のissueが解決したら最新版を使う
# https://github.com/plataformatec/devise/issues/9999999
gem 'devise', '~> 4.5.0'
```

### 4-b. developmentとtestグループのgemを先にアップデートする

アプリケーションの動きに影響を与える可能性が低い、developmentグループとtestグループのgemから先にアップデートします。
以下のコマンドを使うとグループ単位で一気にgemをアップデートできます。

```console
bundle update -g development -g test
```

アップデートしたらテストが全部パスすることを確認します。

### 4-c. トラブルが起きやすそうなgemを1つずつアップデートする

`bundle outdated`の結果を見て、メジャーバージョン番号が変わっているgemや、自分の経験上アップデート時にトラブルが発生しやすいgemを1つずつ慎重にアップデートします。

アップデートを実行する前にリポジトリのCHANGELOGを確認して、トラブルを引き起こしそうな（＝後方互換性が大きく失われるような）変更履歴がないかどうか、チェックすることも重要です。

例：DeviseのCHANGELOG
https://github.com/plataformatec/devise/blob/master/CHANGELOG.md

CHANGELOGを確認したら、gemを1つずつアップデートしていきます。

```console
# 例：deviseだけをアップデートする
bundle update devise
```

gemをアップデートしたら、テストを実行したり、実際に画面を操作したりして、今まで通り使えているかどうかを確認します。

このようにして、トラブルが起きやすそうなgemを1つずつアップデートしていきます。

### 4-d. その他のgemをまとめてアップデートする

トラブルが起きやすそうなgemのアップデートが一通り終わったら、その他のgemをまとめてアップデートします。

```console
# その他のgemをまとめてアップデートする
bundle update
```

なお、この作業を行う前に、Railsのバージョンが固定されていることを確認しておいてください。
（Railsだけは、まだバージョンを変えたくないため）

```ruby:Gemfile
# Railsは特定のバージョンに固定されていること
gem 'rails', '5.2.1'
```

その他のgemをアップデートしたら、テストを実行したり、実際に画面を操作したりして、今まで通り使えているかどうかを確認します。

特に、何らかの理由でテストを自動化できていないロジック（たとえば、テストを書きづらい複雑なドラッグアンドドロップ機能など）は、手動テストを忘れずに行ってください。

最後に、もう一度`bundle outdated`を実行して、ほぼすべてのgemが最新バージョンにアップデートされたことを確認します。
（gemをひとつ残らず最新にするのは無理だと思うので、そこにはこだわらないようにしましょう）

### コラム：gemは普段からこまめにバージョンアップしよう

gemのバージョンアップ作業は実際にやってみると、かなり骨が折れると思います。
古いバージョンのgemが多ければ多いほど、この作業のしんどさが増えるので、日頃からこまめにgemを最新版にアップデートしていくことをお勧めします。

また、そもそもの話ですが、Gemfileに記述するgemを増やしすぎない（＝gemを追加するときは、その必要性を慎重に吟味する）ということも大事だと思います。

## 5.  Rubyのバージョンを最新にする

Railsアプリで使用しているRubyのバージョンが古い場合は、このタイミングで最新にしておくと良いかもしれません。
Rubyは後方互換性をかなり重視しているので、バージョンを上げてもトラブルが起きる可能性は低いはずです。
ですが、それでもゼロとは言えないので、Rubyのバージョンを上げたらテストがすべてパスすることを確認してください。

```console
# rbenvで最新のRubyを使うようにする
# （rbenvとRuby 2.6.3のインストールはすでに終わっている前提）
rbenv local 2.6.3

# gemを再インストール
bundle install

# テストがパスすることと警告が出ないことを確認
RUBYOPT=-W:deprecated bundle exec rspec
```

なお、新しいRailsは古いバージョンのRubyでは実行できません。
たとえばRails 6はRuby 2.5.0以上が必須です。

バージョンごとのRailsとRubyの対応関係はRailsガイドを参照してください。

[Rails アップグレードガイド \- Rails ガイド](https://railsguides.jp/upgrading_ruby_on_rails.html)

## 6. Railsを最新のパッチバージョンに上げる

Railsのマイナーバージョン、またはメジャーバージョンを上げる前に、パッチバージョンを最新に上げます。
たとえば、Rails 5.2.1をRails 6.0に上げる前に、Rails 5.2.3に上げる、という感じです（Rails 5.2.3は本記事執筆時点でのRails 5.2系の最新バージョン）。

```diff:Gemfile
 # Railsのパッチバージョンを最新まで上げる
-gem 'rails', '5.2.1'
+gem 'rails', '5.2.3'
```

```console
bundle update rails
```

パッチバージョンを上げたら、テストがすべてパスすることを確認します。
テストの実行中に警告メッセージが出ていたら、この時点で警告をなくす修正を行います。

なお、Railsのバージョン履歴は、rubygems.orgで確認できます。

https://rubygems.org/gems/rails/versions

## 7. Railsのメジャーバージョン、またはマイナーバージョンを上げる
さて、いよいよRailsのバージョンを上げるときがやってきました。
周辺のgemが最新版になっていれば、比較的すんなり`bundle update`が完了するはずです。

```diff:Gemfile
 # Railsのメジャーバージョンを上げる
-gem 'rails', '5.2.3'
+gem 'rails', '6.0.0'
```

```console
# 新しいバージョンのRailsをインストール
bundle update
```

ただし、バージョンを上げる際は間のバージョンをスキップせずに、1つずつアップグレードしてください。（例：5.0から6.0ではなく、5.1、5.2、6.0と順にアップグレードする）

また、上の例では"6.0.0"になっていますが（本記事執筆時点では6.0.0が最新であるため）、0より新しいパッチバージョンが出ていれば最新のパッチバージョンを指定します。

```ruby:Gemfile
# 0より新しいパッチバージョンが出ていれば最新のパッチバージョンを指定する
gem 'rails', '6.0.2'
```

`bundle update`が終わっても今回はまだテストを実行せず、先に以下の作業を行います。

## 8. rails app:updateタスクを実行する
次に`rails app:update`タスクを実行します。
このタスクを実行すると、新しいバージョンで必要になる新しいファイル作成や、既存ファイルの変更を対話形式で行うことができます。

```console
# 実行例は https://railsguides.jp/upgrading_ruby_on_rails.html より引用
$ rails app:update
   identical  config/boot.rb
       exist  config
    conflict  config/routes.rb
Overwrite /myapp/config/routes.rb? (enter "h" for help) [Ynaqdh]
       force  config/routes.rb
    conflict  config/application.rb
Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
       force  config/application.rb
    conflict  config/environment.rb
...
```

上書き更新されるファイルは次のように対応方法を質問されるので、 Y/n/a/q/d/hのいずれかのキーを入力します。

```console
Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
```

各キーの意味は次の通りです。

```console
Y - Yes。上書き実行
n - No。上書きしない
a - All。このファイル以降の全ファイルを上書き
q - Quit。処理中断
d - Diff。新旧ファイルのdiffを表示
h - Help。入力する各キーの意味を表示
```

僕は`d` = diffを確認した上で、Yかnを入力することが多いです。
（でも、routes.rb以外はほとんどYを入力しているかもしれない）

### 必要に応じて上書きされた設定を元に戻す

Yで上書き実行すると、それまで使っていた重要な設定が失われることがあります。
その場合はgitのdiffをチェックしながら、上書きされて消えてしまった設定を自力で戻していきます。

```diff:config/environments/production.rb
 # 例：上書き更新でコメントアウトされたforce_sslの設定を元に戻す
-# config.force_ssl = true
+config.force_ssl = true
```

ちなみにRubyMineを使うと、GUI上で視覚的にdiffの確認とコードの修正（ロールバック）ができるのでとても便利です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">rails app:update後に上書きされたproduction.rbの設定を、RubyMineを使って一部だけ元に戻す動画です。こんな感じでGUI上でポチポチ元に戻していけるので、とっても便利〜✨ <a href="https://t.co/f0qrUewJHh">pic.twitter.com/f0qrUewJHh</a></p>&mdash; Junichi Ito (伊藤淳一) (@jnchito) <a href="https://twitter.com/jnchito/status/1163300982144372736?ref_src=twsrc%5Etfw">August 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 9. railsdiff.orgを参考にして、新しく追加されたgem等を確認する

`app:update`コマンドを実行しても、Gemfileのようにまったく更新されないファイルもあります。
ですが、`rails new`した直後のGemfileを比較すると、デフォルトでインストールされるgemの種類やバージョンには違いがあります。
Railsのバージョンを上げたのであれば、こういった部分も新しいRailsに合わせておく方が安心です。

そういうときに便利なのが http://railsdiff.org です。
このサイトではRailsの各バージョンごとに、rails newした直後のファイルのdiffを確認することができます。

たとえば以下のURLを開くとRails5.1.7と5.2.3のdiffを確認できます。

http://railsdiff.org/5.1.7/5.2.3

Gemfileを見るとbootsnapのような新しいgemがいくつか追加されているので、この情報を見ながら自力で差分を埋めていきます。

```diff:Gemfile
 # 例：Rails 5.2からはGemfileにbootsnap gemが追加されているので、自分のアプリにもbootsnapを追加しておく
+# Reduces boot times through caching; required in config/boot.rb
+gem 'bootsnap', '>= 1.1.0', require: false
```

他のファイルに対しても、必要に応じて自力で差分を埋めていきましょう。

## 10. 動作確認を行う
そろそろ新しいバージョンのRailsを起動する準備が整いました。
ですが、いきなりテストを動かしても意味不明なエラーが出て面食らうこともよくあります。
なので、僕は次のような順番で動作確認を行います。

### 10-a. rails cが起動するか？
最初は`rails c`（`rails console`）が起動することを確認します。
この時点でエラーが出て起動に失敗する場合は、エラーメッセージをよく読んでエラーを修正します。

rails consoleが起動し、`User.count`のような簡単なコードが実行できればOKです。

### 10-b. rails sが起動するか？
次は`rails s`（`rails server`）が起動することを確認します。
サーバーの立ち上げ時や、サイトアクセス時にエラーが発生する場合は、エラーメッセージをよく読んでエラーを修正します。

画面のデザイン崩れがなく、いくつかの画面が正常に開くようになればOKです。

### 10-c. 全部テストがパスするか？警告も出ないか？
ここまでくればテストコードも問題なく実行できるはずなので、テストを実行してみましょう。

パスしなかったテストがあれば、原因を調べて修正してください。

また、テストはパスしていても、ターミナルに警告文が表示されていることもよくあります。
"DEPRECATION WARNING"のような文字が出力されていないか、テスト実行後のターミナルを確認してください。

警告文には警告の原因やコードの修正方法、詳細な説明ページのURL、警告が発生したコード行等が載っていることも多いので、英語が苦手な人もまずは英文をじっくり読むことが解決への近道になります。

### 10-d. load_defaultsやnew_framework_defaults_x_x.rbを設定する
Railsのバージョンが上がると、従来の挙動とは異なる、新しい挙動が導入される場合があります。
バージョンアップ後はすべて新しい挙動に合わせられるのが理想的ですが、場合によっては一部の挙動を古いRailsに合わせないといけないかもしれません。

こうした挙動の変更は`load_defaults`や`new_framework_defaults_x_x.rb`で行います。

`load_defaults`と`new_framework_defaults_x_x.rb`の関係については以下の記事で詳しく説明しているので、こちらを読んで適切に設定を変更してください。

[config\.load\_defaultsとnew\_framework\_defaults\_x\_x\.rbの関係を詳しく調べてみた \- Qiita](https://qiita.com/jnchito/items/cce3b2795e1c66735310)

### 10-e. 自分の手と目でテストする
自動テストが全部パスしていても100%安心はできません。
テストはパスしていても微妙に画面の表示が崩れているケースもあるので、自分の手と目でも動作確認しておきましょう。

また、画面のデザイン崩れだけでなく、自動テストで担保できていない複雑な機能も手動でテストする必要があります。

この作業が完了すれば、ローカル環境での動作確認はおしまいです。

## 11. ステージング環境にデプロイして動作確認する
次に、ステージング環境（本番環境と同じ構成のテスト環境）にRailsアプリをデプロイします。

ローカルではちゃんと動いていても、デプロイしたらサーバーの起動に失敗した、なんていうこともたまにあります。
その場合はエラーログを見ながら、問題を修正してください。

ステージング環境でアプリケーションが動き始めたら、手動テストを行います。
Amazon S3へのファイル保存や外部APIとの連携機能など、ローカル環境では動作確認しづらい機能があれば、必ずここで動作確認しておきます。

動作確認は1日で終わらせるのではなく、できれば数日間動かしてみて、何か問題が起きないか確認する方が望ましいです。

## 12. プルリクエストを作成し、コードレビューしてもらう
ステージング環境でも動作確認ができたら、本番リリースに向けてプルリクエストを作成し、他のメンバーにコードレビューしてもらいましょう。

もしかすると、コードの修正漏れを指摘されたり、経験者ならではのアドバイスをもらえたりするかもしれません。

## 13. 本番環境にデプロイして動作確認する
いよいよ最後のステップです。
プルリクエストをマージし、バージョンアップしたRailsアプリを本番環境にデプロイします。

ステージング環境と本番環境がまったく同じ構成であればトラブルが発生する可能性は低いですが、念のため、可能な範囲で手動テストしておきましょう。

バージョンを上げたことに起因するトラブルが突然起きる可能性もあるので、リリース後、数日間はログやサーバーのリソース状況等を注意して監視してください。

## まとめ
お疲れ様でした。Railsアプリケーションをアップグレードする手順は以上になります。

アップグレード作業の大変さ（作業開始から完了までにかかる工数）は、アプリケーションの規模やテストのカバレッジ率、gemのアップデートをサボっていた期間の長さ等によって大きく変わってきます。
ですが、運用を続ける限りは「大変そうだから、アップグレードを見送る」という選択肢はまずありません。
技術的負債が雪だるま式に膨らむ前に、早め早めにバージョンを上げていきましょう！

**宣伝：テストが苦手な方は「Everyday Rails - RSpecによるRailsテスト入門」をどうぞ！**

この手順を最後まで読まれた方はよくわかると思いますが、Railsのアップグレード作業には自動テストが必要不可欠です。
「テストを書くのが苦手」「どうやってテストを書けばいいのかわからない」という方はぜひ、僕が翻訳した電子書籍「Everyday Rails - RSpecによるRailsテスト入門」を読んでみてください。
RSpecや自動テストは初めて、という方でもわかるよう、Railsアプリケーションのテストの書き方を詳しく、丁寧に説明します。

「Everyday Rails - RSpecによるRailsテスト入門」は、以下のLeanpubのサイトから購入可能です。

https://leanpub.com/everydayrailsrspec-jp
[![hero.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7465/2ca8d3e0-1ab3-b87f-b034-474d00e3fc58.png)](https://leanpub.com/everydayrailsrspec-jp)