苦しく楽しくやっていく、Ruby on Rails 5へのアップグレード。

こんにちは。好きなRailsのModuleは ActiveModel::Attributes@teitei_tkです。

この記事は freee Developers Advent Calendar 2018 12/10の記事になります。

今回、freeeの認証基盤・アカウント基盤のRails Applicationを4.2系から5系に更新した際の手順を書いていきます。


更新手順を定める

Railsのバージョン更新と言っても、やることはいっぱいあるので、やるべきことをタスクレベルへ分解していきます。

  1. やること・やらないことを決める
  2. リリースノート、アップグレードガイドの確認
  3. 社内製gemの更新作業
  4. OSS gemの更新作業
  5. Railsのアップグレードと、開発ブランチへのバックポート

一つずつ紐解いていきます。

1. やること・やらないことを決める

やる前にまず前提条件を考えます。なぜRailsのアップグレードを行うのか。

なぜか : セキュリティパッチが当たらなくなるであろう想定

Railsのメンテナンスポリシーがありますが、そこで4.2系は一応入ってはいます。が、次のメジャーバージョンがリリースされた後はメンテナンスポリシーから外れてしまいます。

railsguides.jp

また、RailsはRails wayと呼ばれているように新しい機能やバグ修正なども速いスピードで取り込まれています。

上記のことを踏まえると、Railsのアップグレードメンテナンスを怠ると、セキュリティ的な観点からもアップグレードを行いプロダクトを守っていくことが必要になります。 更に利用しているgemでも、同様にセキュリティ対策・バグfix修正を取り込む必要性があり、バージョンアップをしていくことが重要です。

Railsのアップグレードというと新機能に目が行きがちですが、version更新に発生する不具合との切り分けが難しくなり、障害調査が大変になります。

なので今回のバージョンアップでやること・やらないことを決めました。

やること
  • 利用gemのバージョン更新
  • Railsのバージョンアップグレード
やらないこと
  • ActionCableやWebpackerなどの新機能は導入
  • Rubyのバージョン更新

場合によってはRubyのバージョン更新が必要なことがあるかもしれませんが、同様に不具合の切り分けが難しくなるので、Rubyのバージョン更新後にRailsの更新作業を行うことを強くおすすめします。

2. リリースノート、アップグレードガイドの確認

手を動かすにもまずは知識が必要なので、変更するバージョンのリリースノート、アップグレードガイドを確認します。

最初にアップグレードガイドを確認しました。

railsguides.jp

ここでも新機能に目が行きがちですが、主に見るべきは削除されたもの、非推奨の機能の確認です。

Ruby on Rails 5.0 リリースノート | Rails ガイド

Railsは以前のバージョンで非推奨にされたものは時期バージョンで削除されることが多いです。

例: ActiveModel

これを踏まえると非推奨になった機能を application.rb などで再度有効化しても、その次のバージョンでは削除されると予想できます。
なので、Railsには逆らわず、非推奨になった機能は極力修正を加えておきましょう。

アップグレードガイドではBugfixの情報は載っていないので、GitHubの各種CHANGELOGを眺めることもおすすめします。

github.com

あまり関係が無いこともありますが、頭の片隅にでもあるとアップグレード対応時の原因の切り分けに役に立ちます。

2. 社内製gemの更新作業

ここから実際にコードに手を加えて行きます。

プロダクトをRailsで書いている会社さんだと、ドメインロジックを統一するために内製のgemを作り、各アプリケーションで利用しているのではないでしょうか。
弊社でも認証・認可で利用しているgemから、development・proudction環境毎に処理を変更するパラメータの調整用のgemなど様々あります。

まずは比較的影響の少ないであろう内製gem、更に利用箇所が少ないgemの更新作業から行いました。

よく利用するのはActiveModelやActiveSupportだと思いますが、内製gemでもそんなに行儀の悪いコードを書いていない場合は、gemspecのversionを更新するだけで済みます。

ですが、RailsEngineになると話が違ってきます。

RailsEngine製のgemの悩み

RailsEngineで動かす場合、利用側にmigration fileを利用して、tableを作ることが主だと思いますが、 Rails5からは MigrationClass にversionを記載するようになっています。

# for Rails4
class HogeFuaMigration < ActiveRecord::Migration
end

# for Rails5 or later
class FooBarMigration < ActiveRecord::Migration[5.1]
end

Rails5では、migration classにversionがない場合はdeprecate warning、5.1以降だとエラーで死ぬ感じになっています。
今回の場合、Engineを利用する側(各プロダクト)はRails4なのでなので、Migration Classにversionを記載したらいけると思っていましたが、 Rails4.2系だとMigrationClassにVersionを記載するとErrorで死にます。辛いですね。

いろいろ調べてみると同じ悩みを抱えている人がいて、1つのmigrationファイルでRails 4.2系と5.0系両対応する「activerecord-compatible_legacy_migration」というgemを利用することにしました。

https://qiita.com/sue445/items/d096f46103ed21fc7ef6

gemを利用するにあたりstar数は少ないのですが

  • 更新頻度もちゃんと更新されている。
  • コードの量も最悪forkしてメンテナンスできるレベル。

ということで採用するようにしました。

ですが、このままだと名前空間の問題で各プロダクトに activerecord-compatible_legacy_migration gemを入れる必要があり、
もし自分がプロダクトの開発をしている場合、覚えられる自信がなかったので、Rails EngineにMigration Classを定義しactiverecord-compatible_legacy_migrationを隠蔽することにしました。

# lib/example-rails-engine/migration.rb

require 'activerecord-compatible_legacy_migration'

module ExampleRailsEngine
  class Migration < ActiveRecord::CompatibleLegacyMigration.migration_class
  end
end
# lib/example-rails-engine.rb

require "example-rails-engine/migration"

module ExampleRailsEngine
end
# db/migrate/hogefuga.rb

class CreateBillingSummary < ExampleRailsEngine::Migration
end

こうすることにより、MigrationClassをRailsEngineの名前空間内に隠蔽することができ、各プロダクトへgemを入れる必要がなくなります。

3. OSS製gemの更新

Gemfileに記載しているgemのversionを更新することはそこまで難しくないと思います。
多くの場合、Gemfileのversion指定を更新し、bundle update --conservative 'update gem name'と打つだけです。

(なお、--conservative optionに関しては pixivさんの記事で知りました。ありがとうございます。

inside.pixiv.blog

ですが、versionを更新し、動作確認をしているとgemの処理が変更されており、アプリ側でエラーになるときがあります。
そういうときはgemのCHANGELOGを読むとだいたいの変更点が記載されているので、対応ができます。

もし変更点がない場合はPRを送ることも良いと思います。
freeeではActiveRecordのページネーションにkaminariを利用しているのですが、CHANGELOGに記載がないものがあったのでPRを出しました。

github.com

4. Railsのアップグレードと、開発ブランチへのバックポート

いよいよRails本体の更新です。 OSS製gemの更新欄にも記載しましたが、 bundle update --conservative を利用してupdateをしましょう。
そうすることにより、他のgemの更新を抑制することができます。

更新後は、開発を止めるのを避けるために、普段開発しているbranchから新しくRailsアップグレード用のbranchを切って、そこで作業を行います。

Railsアップグレード用のbranchを切った後、そこで作業を行います。 多くはrspecなどのテストが書いてあると思うので、まずはspecが落ちるところを対応していきます。

重要な点ですが、 作業はPR単位で小さく行い、極力Rails4 でも 5でも動くようにしましょう。
そして修正した内容は、 Railsアップグレード用のbranchではなく、普段開発しているbranchに向けます。 開発しているbranchでマージがされた後、Railsアップグレード用のbranchへrebaseをして変更を取り込みます。

理由は、下記の点です、

  • プロダクトの開発用branchの状態とRailsの更新用branchのdiffを極力減らすことでレビュワーの負担を減らす。
  • Rails4でも5でも動くようにし、branch間の差分を減らす。
  • Rails4でも動くようにし、プロダクション環境へ反映することで、アップグレード時の不確実性を減らし、安全に更新できるようにする。

特に、Rails4 でも 5でも動くようにすることが重要です。
Railsの更新作業は長期間になることが多いです。極力差分を減らし、conflictを回避します。

具体的な例を挙げると Rails 4 から 5 でテストで使う HTTP メソッドの引数が変わっています。
github.com

この変更をRails更新用branchでのみ修正を行っていると、プロダクトの開発用のbranchとの差が激しくなり、ほぼ確実にconflictが起きてしまいます。

このような対策として、各種gemがあるので利用をしていきましょう。
今回のケースでは、 rails_kwargs_testing gemを利用させて頂きました。

medium.com

この他にもたくさん行ったことはあるのですが、
全てを上げるとキリがないので、今回は自分の記事へのリンクで申し訳ないのですが、調べて対応していったことを列挙させてもらいます。

note.mu

qiita.com

最後に

Railsのアップグレード作業はとてもやりがいがあると思います。
と同時にとても地味で、孤独な作業です。他の人から何をやっているか分からないという事もあるかもしれないので、
社内のwikiや、ドキュメント管理ツールなどで、共有をしたり勉強会などでLTや登壇をすると、やっていることの可視化と、モチベーションを保つことができるのではないかと思っています。

note.mu

speakerdeck.com

最後に宣伝で申し訳ないのですが、2018/12/07 にfreee on Railsと称してfreeeがRailsを利用していて得られた知見を共有する勉強会がありますので、興味のある方はぜひご参加いただければと思います。

(私はお話はしませんが、企画をさせてもらいました。

freee-tech-night.connpass.com

そして、freeeではRailsにかかわらず技術を使ってバックオフィスを改善していく やっていき手 を募集しています。

jobs.freee.co.jp

次の11日目は、一緒にfreee tech nightを企画した @_kemuridama さんです!