freee 会計でエンジニアをやっている jaxx です。昨年 freee 会計を Rails 6 に上げましたが、今回チーム(@jaxx @kaion @hachi @gakky)で Rails 6.1 へのアップグレードを担当して9月末にリリースしたのでエントリーしました。
目次
- 作業全体の流れ
- まずは feature ブランチで Rails 6.1 に上げてみる
- 依存パッケージのアップデート
- Deprecation Warning の解消
- Breaking Change の解消
- Fail している RSpec の修正
- 最後に
作業全体の流れ
- まずは feature ブランチで Rails 6.1 に上げてみる
- 事前作業として依存パッケージのアップデート
- 事前作業として Deprecation Warning の解消
- Breaking Change の解消
- main_schema.rb を schema.rb に rename する
- Fail している RSpec の修正
まずは feature ブランチで Rails 6.1 に上げてみる
まず、どれぐらいの作業量が必要かわからなかったので、見積もるために雑に Rails 6.1 にあげてみるというブランチを作成し、先行調査しました。
この調査の結果、以下のことがわかりました。
- アップデート・削除が必要な依存パッケージ
- いくつかのパッチバージョンのアップデート
- Braeking Change を含むいくつかのメジャーバージョンのアップデート
- db:structure:load が動かない
- 落ちそうな spec のボリュームと種類
依存パッケージのアップデート
2-1. いくつかのパッチバージョンのアップデート
これらはパッチバージョンだったので Rails6.1 アップグレードの前に先行してアップデートを実施しました。
2-2. 依存する社内gemのアップデート
freeeでは複数プロダクトに渡って共通する機能を社内gemとして切り出すことがあります。 その社内gem内にRails6.1に対応していないものがあったので、リポジトリオーナーに対応をお願いしたり、こちらでPR用意してレビューに入ってもらったりしました。
activerecord-import を v0.25.0 にアップデート
on_duplicate_key_update
がデフォルトで付かなくなるという breaking change がありました- 既に
on_duplicate_key_update:
オプションを指定している箇所については対応不要で、オプションを指定していない箇所の対応が必要でした on_duplicate_key_update: [:updated_at]
とすると既存のSQLと挙動が変わらないため書き換えて対応
Deprecation Warning の解消
Deprecation Warning の列挙については専用に Logger を作成して、プロダクション環境で発生している Deprecation Warning を Datadog 上で集計する方法で進めました。 Datadog上で集計したログはCSVエクスポートして分類した後に、担当をアサインして調査、対応をしていきました。
いくつか Deprecation Warning の対応内容を記載しておきます。
ActiveRecord::Relation メソッドでの安全でない生SQLの利用(非推奨)を削除
- order などのメソッドに直接SQL文字列を渡していると例外になる。
Arel.sql
で囲むという対応になる。が渡ってくるパラメータはsanitize されているとは限らないので、チェックしてからArel.sql
で囲む - ユーザーの入力値をそのままSQLにしているようなケースがあった場合は、そもそも実装を直さないといけないのでチケット新たに切って対応
名前付きスコープをチェーンしたときのスコープが、クラスレベルのクエリメソッドにリークしないようになった
- 対象のスコープを使っているところを洗い出して Rails 6 の状態で
unscoped
をつけると Rails 6.1 と同じ挙動になる - Rails 6.1 にしたあと
unscoped
をつけたままでも挙動が変わらないのでつけたままで問題なし
ActionView::Base#initialize で ActionView::LookupContext でないオブジェクトを第1引数として渡す機能(非推奨)を削除
ActionView::Base.new
の引数がない場合にActionView::LookupContext
の空Objectを渡すActionView::Base.new
をActionView::Base.new(ActionView::LookupContext.new(nil), {}, nil)
に書き換え
where.not が NOR ではなく NAND を述部で生成するようになった
- not に引数を複数渡している箇所がないかを一通りチェックする
- Rails6.1 で where.not に複数 key 指定している箇所を
where.not().where.not
と分割して記述するように修正
render template:に絶対パスを渡せる機能(非推奨)のサポートを削除
PdfView.render_to_string
に渡している layout で絶対Pathを渡している箇所 (ex:Rails.root.join
) を相対Pathに書き換え
Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated
- これは Rails 6.1 から出る deprecation warning なので対応必須ではないものの実装箇所がかなり多いため対応
- 代わりに
ActiveModel::Errors#add
に書き換え、正規表現で一括置換して対応 - https://api.rubyonrails.org/v6.1.3/classes/ActiveModel/Errors.html#method-i-add
Breaking Change の解消
あとは Deprecation Warning に出ていない Breaking change を解消する必要がありました。
いくつか対応内容を記載しておきます。
add_template_helper が使えなくなる
undefined method
add_template_helper' for XXX` というエラーadd_template_helper
が使えなくなるのでhelper
に書き換えることで対応- https://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper
connects_to が absctract_class の後に書かないとエラーになる
connects_to can only be called on ActiveRecord::Base or abstract classes (NotImplementedError)
というエラーself.abstract_class = true
をconnects_to method
より前に定義しないとエラーになる
schema_format を指定しないとエラーになる
- エラーの通り
ActiveRecord::Base.schema_format
を指定することで対応しました
Fail している RSpec の修正
Rails6.1 に上げてみると約 1,500 件の RSpec がエラーになってしまったので、チームで分類して対応していきました。期間としては2週間くらいかかりました。
ActionView::Baseを継承しているサブクラスでnewする際のエラー
■原因
ArgumentError: wrong number of arguments (given 1, expected 3)}
super ActionView::LookupContext.new('app/views')
と類似なのですが、/actionview/lib/action_view/base.rb#initialize
の引数が必須になったため、発生していました。
- Rails 6.0 (actionview: actionview-6.0.4.7)
- Rails 6.1 (actionview: actionview-6.1.6.1)
■ 対応
今回この事象を発見したのがActionView::Baseを継承しており、安直に PdfView.new(lookup_context)
となっているところを PdfView.new(lookup_context, {}, nill)
に書き換えて対応したところ、後続の処理の.render_to_stringでエラーが発生しました。
NotImplementedError: Subclasses of ActionView::Base must implement compiled_method_container or use the class method with_empty_template_cache for constructing an ActionView::Base subclass that has an empty cache.
rails6.1のリリースノートの主な変更を見ると以下の記述がありました。
ActionView::Baseのサブクラスが#compiled_method_containerを実装することが必須化された
そこで以下の書き換えで対応しました。
PdfView.new(lookup_context)
PdfView.with_empty_template_cache.new(lookup_context, {}, nil)
fixture_file_upload method がテストで使えない
■ 原因
■ 対応
このコメントの通り fixture_file_upload('/receipts/sample.png', 'image/png')
であれば以下のように書き換えました。
fixture_file_upload('/receipts/sample.png', 'image/png')
Rack::Test::UploadedFile.new('spec/fixtures/receipts/sample.png', 'image/png')
Psych::DisallowedClass:Tried to load unspecified class: ActiveSupport::HashWithIndifferentAccess
■ 原因
■ 対応
application.rb
に以下の内容を追加
config.active_record.yaml_column_permitted_classes = [Symbol, Hash, Array, ActiveSupport::HashWithIndifferentAccess]
最後に
反省点や改善点などはプロジェクト振り返りでチームでいろいろと議論するとして。 Rails6.1のアップグレードをやっていて良かった点をいくつか挙げると、
- 開始時点では全体のタスクが見えないなかで、とりあえずRails 6.1 に上げてみての事前検証や、DeprecationWarning の Logger を仕込むといった事前準備を、早い段階で思い切って取り組めたこと。
- 調査後の実装についてはボリュームがある部分(Deprecation Warning の解消や Fail している RSpec の修正)を、同期的に集計、分類するグループワークをやった後、効率よく分担して作業に取り組めており、実績の乖離が少なかったこと。
この2点がプロジェクトを進められたポイントだったかなと思っています。
Rails 6.1 にアップグレードしたので、次は Rails 7 ですね!よりスピード感を持って機能開発やカイゼンタスクに取り組んでいこうと思います。今後も努めてまいりますのでご期待ください。
また freee ではプロダクトをより良くするために一緒に働いてくれる仲間を募集しています。ぜひ一緒のチームで新機能開発やカイゼンをやっていきましょう💪