社内で利用している某Railsアプリケーションのバージョンが4.2系たっだのですが、まもなく5.2もリリースされようとしていますのでバージョンアップを決意しました。 (アプリのバージョンアップ中に5.2がリリースされましたが、今回は5.1へのアップグレードです)

実施した結果を先に述べますと影響がもっとも大きいのはモデルに関連した箇所でした。
また、Webpackerを導入しReact.jsのコンポーネントはECMA2015のシンタックスを取り入れることにしたためトラブルが発生するかと思いましたが、 修正量は多いもののそのほとんどが力作業による移行であり大きなトラブルなく進められました。

あらためて感じたことは「テスト重要」ですね。テストがあるおかげで精神的安定を保つことができます。
これからも「テストを全てパスできたのだから大丈夫!」と言えるくらいテストを書かねば・・・

対象のアプリで利用していたRuby、その他ライブラリなどのバージョンは次の通りです。(アップグレード前)

  • Ruby 2.3.6
  • Rails 4.2.4
  • React.js 15.3(react-rails 1.8.2)

当然ですがドキュメントにも記載されているとおり「まずはテストをパスさせましょう」ということで、RspecのモデルとCapybaraによるE2Eのテストが通ることを確認しました。
モデルのテストケース数は440、E2Eの方は89でした。


アップグレードに着手(やり遂げる決意)

社内ツールとはいえ数年利用してきており、社内コミュニケーションの要の1つになっているシステムが対象です。 故にあまり手を入れずにきてしまったのですが、今後のエンハンスを考えると常に最新の状態を維持しておくべきでした。

Rails5.1からはWebpackerの導入によりJavaScriptを導入しやすくなりましたし、システムをさらに使いやすくするなど維持のモチベーションにも繋がると考えられます。
前置きが長くなりましたが、このようなことでアップグレードを決意したわけです。

今回はそれほど大規模なシステムではないので、業務の片手間に2週間程度かけて対応しました。問題の解決に時間がかかりとくじけそうにもなりました。

一度着手したら戻れない(というか無駄にしたくない)ので、「心折れずに最後までやり遂げる」決意も大切です。
(ちょっと大げさかもしれませんがなかなか骨の折れる作業になりましたので)


アップグレードの手順

Railsのアップグレードの手順は公式ドキュメントを参考に進めました。

ドキュメントでは4.2から一気に5.1にするのではなく

  • 一旦4.2から5.0に移行するする
  • 5.0でテストを通す
  • 5.0から5.1へ移行する
  • 5.1でテストを通す

となります。

若干話が逸れますがテストにはRspecを採用していました。昨今minitestの方にしたほうが良いという話もあり、テスト時間も短縮できるようなのでminitestに乗り換えようと考えています。

Rspecはこれまでも社内で利用しておりノウハウも蓄積できてきました。今後受託開発などでminitestを採用せざるを得ないケースも出てくる可能性もあります。 弊社ではまだminitestの利用実績が少ないこともあり今後に備えてという理由が大きいのですが、このシステムもこの際にあわよくばminitestへも移行してしまおうかということで、4.2→5.0→5.1へと一気に(※)上げ、テストをすぐに通したらminitestへ移行!というつもりでいました。
(※段階的にアップグレードしましたが、5.0にアップグレードした時点ではテストやその他システムの修正はほぼ実施していないという意味で「一気に」としています)

結果的には想定以上に時間がかかりminitestへの移行はTODOとして残りました・・・。今回若干無茶をした感は否めないのですが無事移行することができました。   社内のツールであることでちょっとした冒険もできました。テストコードの大切さも改めて感じた次第です。「テスト重要」


備忘録

今回のRails4.2から5.1へのアップグレードにおいてRailsの互換性などの影響のあった箇所を書き出しておきたいと思います。


ActiveRecord::Relation#uniq の非推奨

uniqメソッドは非推奨となりdistinctへ変更しました。(Rails 5.0)

参考:リリースノート


ApplicationRecordを継承

これはみなさんご存知の通りです。バージョン5.0の代表的な変更ですね。
移行ドキュメントの手順にも記載されています。
モデルクラスの親をActiveRecord::BaseからApplicationRecordへ変更しました。(Rails 5.0)

参考:リリースノート(主な変更点)


belongs_toアソシエーション

belongs_toの仕様がかわり、外部キーの存在チェックが行われるようになりました。ここは多くの修正が必要になりました。(Rails 5.0)
単に関連するオブジェクトが存在しない状態も有りますし、必ず関連するモデルが存在するのに関連付けの手順が悪く(新しい仕様上)、ロジックを見直すべき箇所も見つかりました。 当初は今更こんな変更なぜ?と思いつつ修正していましたが、改めて考えてみると設計やプログラミングにも課題がみえてきたと思います。

最初からRails5.0以上で開発しているシステムは困ったことはないので、このような制約が入ることで過去のコードを少しでも洗練された形にできそうです。今ではこの変更に一票という感じています。

外部キーが設定されない可能性がある場合

# before

belongs_to :user

# after

belongs_to :user, optional: true

参考:リリースノート(主な変更点)

関連するモデルのインスタンス生成をnewメソッドではなくbuildを使う(build_user や users.buildなど)ことで解消できる場合も有りました。 関連するモデルを生成する場合はbuildメソッドを仕様すべきでしたね。


Callbackの:if条件オプション

validationなど、コールバックの:if, :unlessの条件オプションに文字列を渡すことは非推奨となりました。(Rails 5.1)
文字列ではなくシンボル、もしくはprocなどに変更しました。

参考:リリースノート(非推奨)


モデルの更新前後の値を取得する

モデルの属性が更新されているかどうか判定するメソッドを利用していましたが、一新され警告が出るようになりました。(Rails 5.1?)
5.2では試していないのですが、リリースノートをみる限りでは旧メソッドが廃止されたような内容が見当たりませんでした。

とは言え、deprecatedの警告が表示されるようになったので変更を行いました。例えば次のようなメソッドです。

# before

content_changed?

# after

saved_change_to_content?

APIのリンクを載せておきます。
http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html


Rspec Stubの書き方

スタブの記述の仕方(これはRspecのバージョンの差異ですね。一応掲載。)

# before

User.stub(:current_id).and_return(admin.id)

# after

allow(User).to receive(:current_id).and_return(admin.id)

ネストモデルのリロード

テストの時に更新した属性がモデルに反映されていないため、リロードしてから検証するケースがありました。
これまでアソシエーションを設定したモデルを取得する際、引数にtrueを指定することでリロードされるという技があったのですがこちらも廃止されたようです。(Rails 5.0?)

# before

article.comments(true)

# after

atricle.comments.reload

Rails 5.0のドキュメントからはbelongs_toメソッドで追加されるassociationメソッドの引数がなくなっていました。

参考:CHANGELOG


まとめ

今回、Railsのアップグレードを通じてあらためてテストが重要であることを確認できました。
テストを書くモチベーションも更に高まります。
ActiveRecordに関連する変更が多いので、バージョンアップの都度リリースノートは確認いておきたいものです。
それからRailsのバージョンアップには早めに追従していきましょう。