Ruby on Rails アップグレード 4.1 → 4.2【Ruby on Rails Advent Calendar 2015】

この記事は Ruby on Rails Advent Calendar 2015 の2日目です。


私は Ruby on Rails のアップグレードについて取り上げさせていただきます。

Rails のアップグレードに関しては、
良質なガイド、紹介記事など、すでに多数存在します。

しかしアップグレードは、なかなか気の重い作業なので、
ついつい後回しにしてしまったり、
どうせ他人に引き継ぐのだからと放置されていたりします。

アドベントカレンダーは、ある種のプログラマーのお祭りだと思います。
ここで取り上げることで、少しは皆様の気に留めていただけるかなと思い、
取り上げることにしました。


Rails のアップグレードって必要?

Rails のアップグレードには脆弱性対応が含まれています。
そして、古すぎる Rails は次第にメンテナンス対象から外れていきます。

こちらによると、現時点(2015年12月2日時点)で
少なくとも バージョン 4.1 以上であることがおすすめです。

古すぎる Rails をアップグレードしようとすると、
それは大変なコストがかかります。
リソースや、使っている gem の状況などから、
アップグレード自体を断念せざるを得ないとなると、状況は最悪です。



と、ちょっと堅苦しい話になってしまいましたが、
今年のうちに Rails のアップグレードを済ませてしまい、
気持よくクリスマスと、新年を迎えましょう!


4.2 へアップグレードする前に

  • ソースコードがバージョン管理(git)されていること
  • Ruby 2.2 を使っていること(現時点の最新は 2.2.3)
  • Rails 4.1 を使っていること(現時点の最新は 4.1.14)
  • 利用している gem は、可能な限り最新バージョンまでアップグレードしておく

を前提としています。

上記に該当しない状態のプロジェクトならば、
まず先に、ここまでアップグレードしてください。


手前味噌で恐縮ですが、昨年のアドベントカレンダーでこんな記事を書きました。

もしよろしければ、ご参考まで。



参考資料1:リリースノート

Rails のバージョンアップによる変更点を、こちらで確認してください。

概略に目を通していただき、
特に「非互換性」の項目は注意深く見ていただければと思います。


参考資料2:アップグレードガイド

アップグレードに取り組む上で、見るべきはこの資料になるかと思います。

こちらは、よく目を通してください。
基本的に、私もこちらで示唆されている方法に従い、アップグレードを毎回行っています。



有名なこちらの記事も貼っておきますね。

STEP 1: クリーンな Rails 4.1 と 4.2 のプロジェクトを準備する

「$ rails new」コマンドを実行し、生成された Rails プロジェクトで、
全く何も手を加えていない状態のことを、
便宜上、「クリーンなプロジェクト」と呼ばせていただきます。

事前にクリーンなプロジェクトを準備しておくと、
アップグレード作業の最中、
4.1 と 4.2 の間で、ソースコードレベルでどのような変更が加わっているのか、
確認するのが大変容易になります。

クリーンなプロジェクトの構築の流れ

次のような流れで、4.1 と 4.2 のクリーンなプロジェクトを準備します。

  • bundler を使い、任意のバージョンの Rails をインストール
  • インストールした Rails で、プロジェクト生成($ rails new)
4.1 のクリーンなプロジェクトを準備

下記のとおりです。

$ cd ~/Workspace/
$ mkdir rails_4_1_clean
$ cd rails_4_1_clean

# Gemfile 生成
$ bundle init
$ vi Gemfile

# Gemfile を下記のように書き換えてください
# > source "https://rubygems.org"
# >
# > gem "rails", "~> 4.1.0"

# 4.1 系の Rails をカレントディレクトリにインストール
$ bundle install --path .
# インストールした Rails を使って、スケルトン生成
$ bundle exec rails new webapp --skip-bundle 

`rails_4_1_clean/webapp` 以下に、4.1 のクリーンなプロジェクトが構築されます。

4.2 のクリーンなプロジェクトを準備

4.2 も、ほぼ同様です。

$ cd ~/Workspace/
$ mkdir rails_4_2_clean
$ cd rails_4_2_clean

# Gemfile 生成
$ bundle init
$ vi Gemfile

# Gemfile を下記のように書き換えてください
# > source "https://rubygems.org"
# >
# > gem "rails", "~> 4.2.0"

# 4.2 系の Rails をカレントディレクトリにインストール
$ bundle install --path .
# インストールした Rails を使って、スケルトン生成
$ bundle exec rails new webapp --skip-bundle 

STEP 2: Gemfile の差分(4.1 → 4.2)を確認

アップグレードによって、初期の gem 構成にどのような違いがあるのか
確認してみましょう。
4.1 と 4.2 の Gemfile の差分はこのような感じになっていました。

左が 4.1.14 の Gemfile、右が 4.2.5 の Gemfile です。

今回の Rails のバージョン間の違いはさほどないようですが、
「gem 'web-console'」というものが増えてますね。



web-console

起動中の Rails アプリケーションに、ブラウザから CUI アクセスすることが出来るようです。
紹介記事のリンクを貼っておきますね。

STEP 3: Rails 4.2 をインストール

Gemfile の rails のバージョン表記を変更します。

-gem "rails", "~> 4.1.0" # 4.1.x latest
+gem "rails", "~> 4.2.0" # 4.2.x latest

その後、bundle update を実行します。

$ bundle update

STEP 4: rails:update コマンドを実行

下記のコマンドを実行します。

$ bundle exec rake rails:update

バージョンアップによって影響を受けているファイルを、
Rails が対話的にアップデート、解決していくコマンドです。

私が実際に実行してみたところ、下記のような結果となりました。

$ rake rails:update
    conflict  config/boot.rb
Overwrite /Users/kosuke/Workspace/hinata/config/boot.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/boot.rb
       exist  config
    conflict  config/routes.rb
Overwrite /Users/kosuke/Workspace/hinata/config/routes.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/routes.rb
    conflict  config/application.rb
Overwrite /Users/kosuke/Workspace/hinata/config/application.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/application.rb
   identical  config/environment.rb
    conflict  config/secrets.yml
Overwrite /Users/kosuke/Workspace/hinata/config/secrets.yml? (enter "h" for help) [Ynaqdh] n
        skip  config/secrets.yml
       exist  config/environments
    conflict  config/environments/development.rb
Overwrite /Users/kosuke/Workspace/hinata/config/environments/development.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/environments/development.rb
    conflict  config/environments/production.rb
Overwrite /Users/kosuke/Workspace/hinata/config/environments/production.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/environments/production.rb
    conflict  config/environments/test.rb
Overwrite /Users/kosuke/Workspace/hinata/config/environments/test.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/environments/test.rb
       exist  config/initializers
    conflict  config/initializers/assets.rb
Overwrite /Users/kosuke/Workspace/hinata/config/initializers/assets.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/initializers/assets.rb
   identical  config/initializers/backtrace_silencers.rb
    conflict  config/initializers/cookies_serializer.rb
Overwrite /Users/kosuke/Workspace/hinata/config/initializers/cookies_serializer.rb? (enter "h" for help) [Ynaqdh] n
        skip  config/initializers/cookies_serializer.rb
   identical  config/initializers/filter_parameter_logging.rb
   identical  config/initializers/inflections.rb
   identical  config/initializers/mime_types.rb
   identical  config/initializers/session_store.rb
   identical  config/initializers/wrap_parameters.rb
       exist  config/locales
      create  config/locales/en.yml
       exist  bin
   identical  bin/bundle
    conflict  bin/rails
Overwrite /Users/kosuke/Workspace/hinata/bin/rails? (enter "h" for help) [Ynaqdh] n
        skip  bin/rails
   identical  bin/rake
      create  bin/setup

Conflict したファイルについて、どうするのか問われますが、
基本的には 'n' (Skip) としてください。

この実行ログは、アップグレード作業中に参照する
大切な記録となります。
コピペして、どこかに保存しておくことをおすすめします。


STEP 5: rails:update で Conflict したファイルを、手動で更新

rails:update で Conflict したファイルを、
1つ1つ差分を確認し、手動で更新していくのが、最も確実だと私は思います。

事前に、4.1 と 4.2 のクリーンなプロジェクトを準備したと思いますので、
それぞれのバージョンを見比べながら、
Conflict したファイルの差分を確認します。

下記に、私の環境で、実際に衝突していた
ファイルの差分を記載しました。

config/boot.rb
-# Set up gems listed in the Gemfile.
 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
 
-require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
+require 'bundler/setup' # Set up gems listed in the Gemfile.
config/routes.rb
  • conflict は発生したが、差分はありません。スルーします。
config/application.rb
+    # Do not swallow errors in after_commit/after_rollback callbacks.
+    config.active_record.raise_in_transactional_callbacks = true
config/secrets.yml
  • confilict は発生したが、本質的には差分がありません。スルーします。
config/environments/development.rb
+  # Asset digests allow you to set far-future HTTP expiration dates on all assets,
+  # yet still be able to expire them through the digest params.
+  config.assets.digest = true

development 環境、デフォルトでアセットに対するダイジェスト値付与が true になっています。
PR → https://github.com/rails/rails/pull/15155

config/environments/production.rb
-  # Disable Rails's static asset server (Apache or nginx will already do this).
-  config.serve_static_assets = false
+  # Disable serving static files from the `/public` folder by default since
+  # Apache or NGINX already handles this.
+  config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
-  # Set to :debug to see everything in the log.
-  config.log_level = :info
+  # Use the lowest log level to ensure availability of diagnostic information
+  # when problems arise.
+  config.log_level = :debug
config/environments/test.rb
+  # Randomize the order test cases are executed.
+  config.active_support.test_order = :random

テスト実行順序の制御が加わっています。

config/initializers/assets.rb
+# Add additional assets to the asset load path
+# Rails.application.config.assets.paths << Emoji.images_path

アセットのロードパスを追加する方法の例(コメント)が増えています。

bin/setup

新しく追加されたファイルです。
PR → https://github.com/rails/rails/pull/15189

これはアプリケーションの初期構築を行うためのスクリプトです。

  • $ bundle install
  • $ bin/rake db:setup

していた場合は、

  • $ bin/setup

だけで済ますことが出来ます。
このスクリプトは、それぞれのアプリケーションの構築手段に合わせて、
気軽にカスタマイズできるようです。


STEP 6: テスト実行

rails:update で更新されたファイルの見直しが終わったら、
次はテスト(RSpec, MiniTest)を実行します。

これにより、テストに失敗したり、
「DEPRECATION WARNING」の発生を見つけたりすると思います。

テストの失敗は、地道に原因を突き止め、修正を行います。

DEPRECATION WARNING については、例えば次のような感じで発生します。

DEPRECATION WARNING: The configuration option `config.serve_static_assets` has been renamed to `config.serve_static_files` to clarify its role (it merely enables serving everything in the `public` folder and is unrelated to the asset pipeline). The `serve_static_assets` alias will be removed in Rails 5.0. Please migrate your configuration files accordingly. (called from block in <top (required)> at /Users/kosuke/Workspace/hinata/config/environments/test.rb:16)

こちらも、大抵の場合、検索すれば対策を見つけることが出来ますので、
警告が消えるまで根気よく修正を行います。


STEP 7: DB マイグレーションの実行

テストが成功するようになったら、DB マイグレーションも一通り実行してみることを
個人的におすすめしています。

$ bundle exec rails db:drop
$ bundle exec rails db:create
$ bundle exec rails db:migrate

特に今回の場合、Rails の SchemaDumper に変更があったため、
schema.rb の出力結果が変化しているはずです。

私の環境の場合、実際に次のような差分が出ていました。

-  create_table "auto_signins", force: true do |t|
+  create_table "auto_signins", force: :cascade do |t|
-    t.integer  "user_id",               null: false
+    t.integer  "user_id",    limit: 4,  null: false
-    t.string   "name",                                   null: false
+    t.string   "name",            limit: 255,             null: false
-    t.text     "body"
+    t.text     "body",            limit: 65535

まず、`force: true` → `force: :cascade` という変更があります。

次に、integer, string, text などに 全て limit が付与されるようになりました。
これは DB の型のデフォルトサイズに合わせて、limit が与えられているようで、
殆どの場合は、このまま反映しても問題は起きないと考えられます。

STEP 8: 実際にアプリケーションを起動、実行してみる

いよいよアプリケーションを起動します。

$ bundle exec rails s
=> Booting WEBrick
=> Rails 4.2.5 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-12-03 02:32:06] INFO  WEBrick 1.3.1
[2015-12-03 02:32:06] INFO  ruby 2.2.3 (2015-08-18) [x86_64-darwin14]
[2015-12-03 02:32:06] INFO  WEBrick::HTTPServer#start: pid=5929 port=3000

一通り、アプリケーションの機能をテストしてみます。
これで問題がなければ、そのままアップグレード作業は、完了です。

ちなみに私の環境では、

DEPRECATION WARNING: Extra .css in SCSS file is unnecessary. Rename /Users/kosuke/Workspace/hinata/app/assets/stylesheets/external/_material_button.css.scss to /Users/kosuke/Workspace/hinata/app/assets/stylesheets/external/_material_button.scss. (called from _app_views_layouts_application_html_erb__2891899565975441164_70316367097260 at /Users/kosuke/Workspace/hinata/app/views/layouts/application.html.erb:12)

という、警告が出ました。
Sass を `main.css.scss` のようなファイル名にしている場合、
これを `main.scss` とリネームしなければなりません。


お疲れ様でした!

以上で、Rails のアップグレードは終りとなります。

実際には、検証環境にデプロイするなどして、
もう少し時間をかけて検証することになります。


私がアップグレードを行った時、実際の Git のコミットログはこんな感じでした。

commit 7edbb8fe246bf23fe67e0c76be49ff5928bcfbbb
Author: komiyak
Date:   Wed Dec 2 01:50:06 2015 +0900

    Capistrano で ruby バージョンを固定している箇所があったのを修正

commit f63a87f1ea1120c3d3e85aabe7081eb5020b8a45
Author: komiyak
Date:   Wed Dec 2 01:21:03 2015 +0900

    db:migrate で schema.rb に変更が入ったため、更新

commit 27df52ee27255a7e87a64db28eafc87c0b5ae159
Author: komiyak
Date:   Wed Dec 2 01:12:43 2015 +0900

    `css.scss``scss` へファイルリネーム

commit 924349c119877b4f7dbcc7728c87df6d1f235149
Author: komiyak
Date:   Wed Dec 2 01:02:05 2015 +0900

    非推奨(DEPRECATION WARNING)の対応

commit 2e0720b7cc30773f85680965791f01c0e94f0a20
Author: komiyak
Date:   Wed Dec 2 00:55:48 2015 +0900

    update monkey patch

    アップグレードによって、モンキーパッチを当てていた部分に問題が出たため、そこを修正

commit 96890071de7bf90e09a8761d548d9c1c9f3c268f
Author: komiyak
Date:   Wed Dec 2 00:55:34 2015 +0900

    upgrade rails 4.1 -> 4.2

commit 8e4d4e937a517ef1cae7aa1f8b597c28af616e31
Author: komiyak
Date:   Tue Dec 1 22:35:26 2015 +0900

    既存の gem を最新バージョンまでアップグレード

    `$ bundle update` 実行し、問題がなくなるまで修正を行う


Rails のバージョンアップが大切だと思っていなかった方、
Rails のバージョンアップを諦めている方、
Rails のバージョンアップをするのが気が重いという方、
すっかり忘れていた方、

そういった方々の、アップグレードに取り掛かるきっかけとなれれば幸いです。

保守されていない Rails アプリケーションが、
この世から少なくなるように願いを込めて、今回の記事を締めくくりとさせていただきます。




ご清聴ありがとうございました。
引き続き、Ruby on Rails Advent Calendar 2015 をお楽しみください!