Ruby on Rails の大掃除! 3.2 → 4.1 にバージョンアップするの巻

追記:続編です → Ruby on Rails アップグレード 4.1 → 4.2【Ruby on Rails Advent Calendar 2015】

これは Ruby on Rails Advent Calendar 2014 の12日目の記事です。
昨日は takeyuweb さんの Active Job meets Amazon SQS でした。

こんにちは。komiyak です。
これは、Ruby on Rails Advent Calendar の参加記事です。

12月といえば Advent Calendar
残り少ない 2014 年。
年越しに向けてラストスパートをかけて、
晦日は、ガキ使でも見ながら、ゆっくり天ぷら蕎麦をすすりたいものですね。

さて、皆様がこの時期にやってしまいたいことといえば、
積もりに積もった1年の汚れを落とす、大掃除ではないでしょうか。

実はわたし、昨年(2013年の暮)、
年末ギリギリまで仕事に追われてしまい、家の大掃除ができませんでした...。

そこで先日、好奇心にかられ、
キッチンの換気扇部分を覗いてみたのですが、
ここは地獄の入り口か?
という惨劇になっておりまして、
閻魔大魔王が、口をお開きになっているのかと勘違いいたしました。

今年の大掃除は、たいへん憂鬱です。




さて皆さま、Ruby on Rails ☓ 大掃除 とくれば、思い描くは、
「積もりに積もった Rails のバージョンアップ」 ではないでしょうか?

Rails 1系、2系、3系は、もう時代遅れです。
思い切って 4系 にバージョンアップし、気持よくクリスマス、新年と迎えていきたいですね。

もうすぐ Rails 4.2 がやってきます!

さぁ、皆さん。
この機会に、私と一緒に Rails を 4.1 系までアップデートしましょう!




この記事の概要について

すっかり前置きが長くなってしまいましたが、皆さま読み飛ばして頂けましたでしょうか?

この記事は、私が運用しているサービスの Rails のバージョンを
3.2.19 → 4.1.8 までアップデートした内容を綴った、顛末記となっております。

できるだけ丁寧な説明を心がけようと思いますが、
詳細なアップデート情報を、網羅的に解説することを、本記事では目指していません。
より正確な情報は、後述するウェブページのほうが優れております。

こんなふうにアップデートしてみたけど、大変だったー!
という苦労自慢のような記事になってしまいましたが、
これを契機に、古いバージョンで動いている Rails をアップデートしてみるかという
皆さまの動機になれば幸いです。


前提条件

スムーズに、トラブルは最小限に Rails のバージョンアップを行うため、
次の内容を前提条件とさせていただきます。

Ruby on Rails をアップデートする際、最初に見るべき所

バージョンアップに伴う変更点を確認します。

この内容を、はじめから全て熟読しておく必要はありません。
バージョンアップ作業中に、問題に突き当たった時に、
読みなおしたりすると、有益な情報を見つけることがあったりします。

ブラウザで、開きっぱなしにしておくとよいかと。



バージョンアップの手順書

手順書は、 Ruby on Rails GuidesRuby on Railsアップグレードガイド が有名です。
joker1007 さんの記事も、とても参考になりますので、下記にご紹介します。

安全なバージョンアップのために

Rails のバージョンアップは、数字を1つずつ上げていくのが一番安全です。

今回だと、3.2 → 4.0 → 4.1 のように段階的にバージョンアップと検証を行いました。
3.2 → 4.1 のように、いきなり間のバージョンを飛ばしてしまうと、それは困難を伴います。
オススメできません。



バージョンアップ(3.2 → 4.0)の準備

作業に入る前に、2つの準備をしておきたいと思います。

  • 3.2 系の最新のバージョンを使っているか?
  • 3.2 と 4.0 系のクリーンなプロジェクトを用意する
3.2 系の最新バージョンを使っているか?

4.0 へバージョンアップする前に、現在のプロジェクトが 3.2 系の最新のもので
問題が起きないかを確認します。
例えば、現在の Rails のバージョンは、次のような方法で確認できます。

$ cat Gemfile.lock | grep rails
    coffee-rails (3.2.2)
    jquery-rails (3.1.2)
    rails (3.2.21)
    sass-rails (3.2.6)
  coffee-rails (~> 3.2.1)
  jquery-rails
  rails (= 3.2.21)
  sass-rails (~> 3.2.3)

RubyGems.org を見ると、現時点での最新の 3.2 系は「3.2.21」でした。
これは問題なさそうなので次のステップへ移ります。


3.2 と 4.0 系のクリーンなプロジェクトを用意する

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


3.2 と 4.0 のクリーンなプロジェクトを事前に用意しておくと、
現在のプロジェクトとの差分、3.2 と 4.0 の差分を、
かんたんに確認できて、とても役に立ちます。

例えば、バージョンアップを行おうとしているプロジェクトの
config/application.rb に、いろいろと変更が加えられているとします。

そんな時、クリーンなプロジェクトと比較しながら作業をすると、
どれが意図的に行われた変更なのか、一目瞭然というわけです。



クリーンなプロジェクトの構築方法

蛇足かもしれませんが、クリーンなプロジェクトの構築方法を、簡単に紹介します。
説明が不要な方は、こちら読み飛ばしてください。

構築の流れは、次のようなものです

  • bundler を使い、任意のバージョンの Rails をインストール
  • インストールした Rails を使い、プロジェクトを生成

下記の操作を順次行ってください。

$ cd ~/workspace/
$ mkdir rails_3_2_clean
$ cd rails_3_2_clean

# Gemfile 生成
$ bundle init
$ vi Gemfile
# Gemfile を下記のように書き換えてください
# > source "https://rubygems.org"
# >
# > gem "rails", "~> 3.2.0"

# これで 3.2 系の最新が bundle install されます
$ bundle install --path .
# インストールされた rails を使って、rails のスケルトンを生成
$ bundle exec rails new webapp --skip-bundle 

これにより 3.2 系の Rails のインストールし、
3.2 系のクリーンなプロジェクトを生成することができます。


同様の手順で、4.0 のクリーンなプロジェクトを生成します。
最終的に下記のような感じになるかと思います。

$ ls
> rails_3_2_clean		rails_4_0_clean

バージョンアップ(3.2 → 4.0)

まず手始めに、クリーンな状態の 3.2 と 4.0 の Gemfile を見比べてみます。
果たして、どのような更新が加わっているのか...?

すべての変更点を説明すると、いささか冗長化と思いますので、
一部の主要な gem のみ紹介いたします。



turbolinks

turbolinks は、ページが表示されるまでの体感速度を向上するため、
JavaScript 等を駆使して、いろいろとトリッキーなことをしてくれます。

turbolinks | RubyGems.org
rails/turbolinks - GitHub


私は使ってません。
(はっきり言って、これの使い所、難しいんじゃないかと思っています)

詳しい動作原理については、例えばこちらなどを参照ください。
もし、turbolinks を使わないならば、こちらの記事を参照ください。


jbuilder

json のためのビューテンプレートで、
API サーバーとして json を返すことが多いアプリケーションで重宝するのかなと。

jbuilder | RubyGems.org
rails/jbuilder - GitHub

3.2 の時から Gemfile に記述はありましたが、コメントアウトされていました。
4.0 からはデフォルトで ON のようです。
もし jbuilder を使っていないのであれば、
Gemfile から jbuilder の記述を削除するだけでよいそうです。


その他の変更点
  • bcrypt-ruby → bcrypt へ変更された
  • sdoc が追加された
Gemfile を rails 4.0 に書き換え、bundle update

Gemfile を下記のように変更します。

旧)
gem "rails", "~> 3.2.0" # 3.2.x latest

新)
gem "rails", "~> 4.0.0" # 4.0.x latest

bundle update を実行。

$ bundle update
rake rails:update コマンド

bundle update に成功したら、rake rails:update コマンドを実行します。

これは Rails に最初から入っている、バージョンアップ作業用のコマンドです。
このコマンド実行により、config ファイルのアップグレードや、
いくつかの初期ファイルの自動生成を行います。

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

更新が終わったら、
git の差分、クリーンなプロジェクトとの差分を参考にしながら、
具体的にどういう変更が加わったのか、詳しく見ていきます。



コンフリクトが起きたのは、主に以下のポイントですね。

  • config/initializers/ ディレクトリ以下
  • config/application.rb
  • config/environment.rb
  • config/environments/*.rb
追加: config/initializers/filter_parameter_logging.rb

任意のパラメータを、ログ出力しないようにするためのフィルター設定が、
initializers/ ディレクトリに移動になりました。
この設定は、3.2 では config/application.rb に書かれていました。

# Be sure to restart your server when you modify this file.

# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password]
変更: config/application.rb

3.2 と 4.0 の際立った変化は、ほぼ設定が空っぽになっているところでしょうか...。

Rails のデフォルトになったため削除されたか、
config/environments/ 以下に移動された、
もしくは config/initializers/ へ移動されたという感じでしょうか。

私は application.rb にあまりごちゃごちゃ設定書くなよ。
というお告げだと受け取りました。
今後、application.rb にはあまり設定を増やさないようにしようと思っています。


変更: config/environments/development.rb, production.rb, test.rb

この辺りは、衝突が大きくなりがちな箇所です。
差分を注意深く見ながら、
不要そうな設定であれば削除。
不明なものがあればググるという感じで、黙々と進めます。


追加: bin/

3.2 以前は、script/rails というファイルでしたが、
4.0 から bin/ ディレクトリへ移動されています。

script/ ディレクトリは、そっと削除した。


変更: テストの構成

4.0 から test/ ディレクトリの構成が変わっているので、その対応を行います。
unit, functional テストのディレクトリが次のように変化しています。

  • 旧)test/unit/ → 新)test/models/
  • 旧)test/functional/ → 新) test/controllers/

あと、test/performance/ ディレクトリが消えてます

  • 削除)test/performance/

もう一点、見落としがちな所ですが、
test/test_helper.rb の内容が微妙に変化しています。

test_helper.rb を自分で拡張していない場合は、
4.0 の内容でそのまま上書きしてしまったほうが、無難かと思われます。



テストを実行する

設定ファイルの見直しがひと通り終わったら、
テストを実行します

そして、それはたぶん... コケます.. (´;ω;`)

コケた箇所を地道に調べて、
コツコツと修正していきましょう。



もし時間的な余裕があるならば、
見落としているテストなどを、このタイミングで追加していくのも
善い行いのなのではないかなと思います。



ちなみに、テストを実行するときは、
念のため db:drop し、db:create, db:migrate の流れも試してみてください。

私は、db:migrate でエラーをひとつ見つけました... (^^;



Strong Parameters と Mass assignment セキュリティ問題

3.2 から 4.0 へアップグレードするにあたって、
Mass assignment セキュリティ問題について、正しい理解が必要です。

4.0 から、Mass assignment の対策として Strong Parameters という機能が導入されました。

この Strong Parameters への対応は、
既存のプロジェクトの規模が大きいほど、手間もかかります。
これもコツコツと、移行作業を進めていきましょう。



バージョンアップの完了

テストが通れば、
あとは実際にアプリケーションを触りながら、動作チェックを行います。
問題がなさそうであれば、バージョンアップはひとまず完了ということになります。

お疲れ様でした。

参考になるのかわかりませんが、
この項の最後に、自分が Rails 3.2 → 4.0 移行作業をした時の git ログを、
下記に残しておきます。

  1. rails version up (3.2 => 4.0) ... Gemfile を整理、rails のバージョンを 4.0 へ
  2. rails 4.0 で追加された bin/ ディレクトリを追加
  3. rake rails:update を実行した結果をコミット ... コンフリクトも全て解消済み
  4. routes.rb で match を使っていたが、rails 4.0 では使えなくなっていたので代替処理にした
  5. schema.rb の構文が少々変化していたので、それを適用
  6. test/ ディレクトリ以下の構成を、rails 4.0 系の構成へ変更
  7. test:models の実行に失敗していたのを修正
  8. doc/README_FOR_APP が削除されたようなので、削除
  9. README の内容が更新されたようなので、それを反映
  10. attr_accessible を使っている箇所を削除、Strong Parameters へ移行
  11. test:controllers の実行に失敗していたのを修正
  12. ActiveRecord の has_many の記法に、一部非推奨のものがでてきたので、それを修正
  13. 古くなって存在していないテーブルのモデルが残っていたのを削除(リファクタリング

バージョンアップ(4.0系 → 4.1系)

この勢いで、4.1 のバージョンアップへ突入しましょう!

私の場合は、3.2 → 4.0 へのバージョンアップで結構苦戦しましたが、
4.0 → 4.1 はすんなり行きました。

個人的な感想としては、
Rails のバージョンアップの難易度は、
利用している gem の種類と、
どの程度 Rails 本体に拡張を入れているかに、大きく依存する気がしています。



早速 4.1 のクリーンなプロジェクトを準備して、
差分を見ながら、バージョンアップ作業を進めましょう。
基本的な流れは、3.2 → 4.0 への移行の時と変わりません。
なので、ここから先の説明は、より簡略化させていただきます。

まず、お約束の 4.0 と 4.1 の Gemfile の差分です。


細かな点を除くと、4.0 と 4.1 の違いは 'spring' の有無のようです。


spring

rails コマンドや、rake コマンドを実行すると、
少し待たされる時間があり、体感的に、少しもたついているような印象がありました。

これはコマンド実行時に、ロード処理などの事前準備が動いているからなのですが、
そこを高速化するために導入されたのが Spring という gem です。

アプリケーションプリローダーと呼ばれているのですが、
詳しくはこちらを参照ください。



追加: config/initializers/assets.rb

4.1 では config/environments/production.rb に定義されていた
config.assets.version の設定が、
config/initializers/assets.rb に追い出されています。

# Be sure to restart your server when you modify this file.

# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'

# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
追加: config/initializers/cookies_serializer.rb

4.1 から、Cookiejsonシリアライズするのがデフォルトになっているようです。
詳しくは、こちらをご参照ください

# Be sure to restart your server when you modify this file.

Rails.application.config.action_dispatch.cookies_serializer = :json
削除: config/initializers/secret_token.rb

config.secret_key_base の設定が、4.1 から消えました。

その代わりに rake rails:update コマンドを実行した時に気がついたかと思いますが、
'config/secrets.yml' ファイルが増えています。

secret_key_base の設定は、こちらに移動しています。



変更: config/database.yml

些細な事ですが、config/database.yml の書き方が、すこし変わりました。
4.0 以前は、単純な要素の列挙だったのですが、
yaml のアンカーとエイリアスが使われるようになりました。

# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

設定ファイルの見直しが終われば、
あとはテストを実行し、アプリケーションの挙動を確認するだけです。
この辺りは、とにかくコツコツと進めていくしかありません。



rake rails:update を実行したら、あとはテストを通して、
アプリケーションの挙動を、逐次確認と修正を行っていきましょう。



おめでとうございます!

無事に、Rails のバージョンアップという
年末大掃除を乗り越えられた皆さま。
たいへんお疲れ様でした m(_ _)m

これで気持よく 2015 年を迎える準備が出来ましたね。


今回の Rails バージョンアップを通して、
今後のバージョンアップ(4.2、5.0)に備えて、
日頃から準備をしておくことも大切だなと思いました。

個人的な知見ですが、次のようなことを心がけていきたいなと思います。



バージョンアップのためのブランチを用意して、そこで最新の Rails バージョンを動かしておく

他のサイト様でも、よく言われているのを目にしました。

今回作業をしてみて、これは価値があると思いました。
こうすることで、非推奨のコードも早めに知ることができますし、
共同で作業しているメンバーにも、Rails のバージョンアップを意識させる効果もあります。


えっ?

git を使ってない...?

git を使いなさい!



Ruby のバージョンを上げる

Rails のバージョンをあげると、
必然的にサポート対象から外れる Ruby のバージョンも増えていきます。

こちらも定期的なバージョンアップをお願い致します m(_ _)m




gem の選定に気をつける

「gem を入れれば超楽勝じゃーん!」とガンガン gem を突っ込んでいくのは考えものです。
確かに短期的なコスト対リターンは良いかもしれませんが、
Ruby, Rails のバージョンアップで、使えなくなってしまうものも結構あります。

その時の修正コストは大きなものになるかもしれませんし、
それによって Ruby, Rails のバージョンアップを断念することになれば、本末転倒でもあります。

オープンソース文化、gem の文化は素晴らしいものだと思います。

ただし安易に頼りすぎるのではなく、節度と責任をもってご利用ください。



変更を最小限に保ったほうがよいファイルがある

config/initializers/ ディレクトリ以下、application.rb、environments/*.rb などは、
Rails のバージョンアップで変更が加わることが大きい箇所です。

プロジェクトの独自の設定変更が、少ないほど、移行の痛みは減ります。

個人的には、
config/initializers の初期生成ファイルは基本的には改変しない。
environments 系の設定の変更は、本当に必要かどうかをよく考えて行おうと思います。

なんとなくその時の都合で、設定をバチバチ書き換えていると、
知らず知らずに Rails らしくないやり方で開発が進んでしまい、
バージョンアップで躓いてしまう.. ということになりやすいのではないかな?と考えています。



Rails コミュニティの情報収集、Release Notes に目を通す

これができると、正確な情報を素早く入手できます。
... わかってはいるのですが、ブックマークしただけで満足してしまい、
なかなかちゃんと情報を追えることは稀です。。(※私のことです)

上記で紹介した、バージョンアップ用のブランチを常に用意しておくなどすると、
当事者意識で、今までよりは情報収集に目が向くようになるのではないかと思います。



Ruby on Rails のアップデートって、必要ですか?

長々とお付き合いありがとうございました。
最後になりますが、Rails のアップデートって本当に必要か?
という話で閉めさせて頂こうと思います。

Rails は古いバージョンのセキュリティパッチが提供されています。
しかし、それもいずれ終わってしまいます。
現時点でも、3.1.x 以前の Rails はセキュリティパッチのサポート対象から外れています

また Rails で運用しているプロジェクトが増えてきた時に、
プロジェクトごとに Rails のバージョンが違いすぎていると、
書き方を切り替えなければいけないので、ストレスになります。




Rails をプロジェクトに採用するかどうかの判断をするときは、
Rails は先進的な仕組みを受け入れ、ドラスティックに変化していく
フレームワークであるという点を考慮するべきです

Rails は初心者のためのフレームワークではありません。


特に、一度導入しまったあとは、メンテをあまりできないようなプロジェクトでは、
個人的にはオススメできないと思っています。

仕組みを変えたくないけど、セキュリティアップデートだけは受け続けたいという
ニーズにはあまり強くありません(Rails の強みはそこではない)。



長い目で見て、継続的にメンテナンスできると思えるサービスに採用してください。
Rails を採用するということは、変化を受け入れるという姿勢を採用することでもあります。
バージョンアップを止めてしまうと、そのプロジェクトは緩やかな死へと向かっていきます。



それではこのへんで。
皆さま、ありがとうございました!



Ruby on Rails Advent Calendar 2014
明日は reikubonaga さんです。