freeeの開発情報ポータルサイト

freee 会計を Rails 6.0 から Rails 6.1 にアップグレードしました

freee 会計でエンジニアをやっている jaxx です。昨年 freee 会計を Rails 6 に上げましたが、今回チーム(@jaxx @kaion @hachi @gakky)で Rails 6.1 へのアップグレードを担当して9月末にリリースしたのでエントリーしました。

developers.freee.co.jp

目次

作業全体の流れ

  1. まずは feature ブランチで Rails 6.1 に上げてみる
  2. 事前作業として依存パッケージのアップデート
  3. 事前作業として Deprecation Warning の解消
  4. Breaking Change の解消
  5. main_schema.rb を schema.rb に rename する
  6. Fail している RSpec の修正

まずは feature ブランチで Rails 6.1 に上げてみる

まず、どれぐらいの作業量が必要かわからなかったので、見積もるために雑に Rails 6.1 にあげてみるというブランチを作成し、先行調査しました。

Rails 6.1 に試しに上げてみたPR

この調査の結果、以下のことがわかりました。

  • アップデート・削除が必要な依存パッケージ
    • いくつかのパッチバージョンのアップデート
    • 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.newActionView::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

Breaking Change の解消

あとは Deprecation Warning に出ていない Breaking change を解消する必要がありました。

いくつか対応内容を記載しておきます。

add_template_helper が使えなくなる

connects_to が absctract_class の後に書かないとエラーになる

  • connects_to can only be called on ActiveRecord::Base or abstract classes (NotImplementedError) というエラー
  • self.abstract_class = trueconnects_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の引数が必須になったため、発生していました。

■ 対応

今回この事象を発見したのが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

■ 原因

Upgrading to Ruby 3.1 causes Psych::DisallowedClass exception when using YAML.load_file - Stack Overflow にある通りでした。

■ 対応

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 ではプロダクトをより良くするために一緒に働いてくれる仲間を募集しています。ぜひ一緒のチームで新機能開発やカイゼンをやっていきましょう💪

jobs.freee.co.jp