TUNAG の Rails バージョンが 7.0 になりました

プラットフォーム部 DevEx チームの河井です。
8月に弊社サービス TUNAG(ツナグ)で使っている Ruby on Rails のバージョンを 6.1 から 7.0 に上げたので共有します。

やったこと

一般的なバージョンアップのフローについては多くの記事がありますので、ここでは影響が大きかった仕様変更の対応方法について紹介します。

フォーマット指定なしの to_s

to_s にフォーマットを渡すことが非推奨化されました。
この変更により、引数なしの to_s の挙動が変わってしまう問題がありました。

そこで、影響を受けるクラスについて to_s をオーバーライドし、フォーマットがこれまでと変わらないようにしつつ、警告を出すことで後から直すべき箇所を見つけやすくしました。

class ActiveSupport::TimeWithZone
  alias original_to_s to_s

  if Rails::VERSION::MAJOR == 6
    # Rails6ではNOT_SETが定義されていないのでここで定義する
    NOT_SET = :default
  end

  def to_s(format = NOT_SET)
    if format == NOT_SET
      # 引数なしのto_sはActiveSupport側でwarningされないので、ここでwarningする
      ActiveSupport::Deprecation.warn('Since Rails7, the format of ActiveSupport::TimeWithZone#to_s with no arguments has changed. To output the same format as before, please use #to_formatted_s instead.')
      to_formatted_s
    else
      original_to_s(format)
    end
  end
end

という対応をして無事 Rails のバージョンを 7.0.6 に上げた直後、7.0.7 がリリースされ、上記と同じ対応が入りました。

github.com

そのため今から Rails 7.0 に上げる場合は上記対応は不要なのですが、記録として残しておきます。

relation.merge

relation1.merge(relation2) という書き方をしたときに、同じカラムに関する WHERE 条件が AND で結合されたり、relation2 の条件で上書きされたりするという仕様になっていました。
Rails 7.0 からは Hash#merge と同じように常に上書きされる仕様に変更されました。

# Rails 6.1 (IN句はマージする側の等値条件によって置き換えられる)
Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]
# Rails 6.1 (競合する条件がどちらも存在する: 非推奨)
Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => []

# Rails 7.0 (IN句の振る舞いは同じで、マージされる側の条件が常に置き換えられる)
Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]
Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [bob]

Ruby on Rails 7.0 リリースノート - Railsガイド

AND として merge を使っていた場合(上記コード例「競合する条件がどちらも存在する: 非推奨」のパターン)の対応として、relation.and を使う方法があります。

github.com

relation.and の注意点としては、relation.or と同じように、クエリの構造が同じでないとエラーになるということがあります。
今回 TUNAG で対応が必要だったケースは、AND として merge を使っているケースで、かつ2つのリレーションの構造を同じにすることが難しい状況でした。
最終的に、クエリを修正して WHERE 条件が被らないようにすることで、 merge を使っても挙動が変わらないようにしました。

timestamp 型の precision

Rails 7 から datetime 型の precision のデフォルト値に変更がありました。 以前は無指定だと DB デフォルトだったのが、7 からは無指定だと precision: 6、nil を指定すると DB デフォルトになるという仕様になりました。

schema.rb を使っている場合は互換性維持の手段があるようですが、弊社では ridgepole を使っており、以下の例の after のようにスキーマの修正が必要でした。

# before
t.datetime :column_a                 # 無指定: DB のデフォルト precision
t.datetime :column_b, precision: 6

# after
t.datetime :column_a, precision: nil # nil 指定: DB のデフォルト precision
t.datetime :column_b                 # 無指定: precision == 6

参考: https://github.com/ridgepole/ridgepole/blob/8b7ea6844a118deb6c4cf9bedcab00a37a56a1f9/README.md?plain=1#L16-L18

安全にバージョンアップを行うために

前回 Rails を 5.0 から 6.1 にしたときは、大量の修正を含んだ Pull Request をマージする運用になっていたため、1週間以上社内限定で動かすなど慎重なリリース作業が求められていました。

一方で今回は、事前にバージョンアップできる gem を更新したり、Rails のバージョン分岐を用いて Rails 6 のうちから 7 でも動くような修正をしたりと、細かく修正を積み重ねていきました。(Pull Request の数は gem のアップデートで 20 件、コードの修正で 30 件ほど。)

CI 環境で検知できるものは先に対応した上で、さらにテストでカバーできていない箇所を拾うため、ActiveSupport::Deprecations を活用して、production 環境で非推奨なコードが実行されたことを検知できるようにしました。

# config/environments/production.rb

ActiveSupport::Deprecation.disallowed_behavior = [:log]

ActiveSupport::Deprecation.disallowed_warnings = [
  /some_method/,
]

結果として、前回行ったような重厚なリリース作業というものはなく、通常の機能追加と同じような感覚でバージョンアップを行うことができました。

まとめ

本記事では、弊社サービス TUNAG を Rails 7.0 にバージョンアップする際に直面した主要な仕様変更と、それに対する具体的な対応策を紹介しました。
記事を公開するタイミングで既に Rails 7.1 Beta 1 がリリースされていましたが、次回からは更にタイムリーに情報を提供できるよう、新しいバージョンに向けて継続的に取り組んでいきます。

最後に、スタメンではエンジニアを募集しています。興味をもっていただけましたら、ぜひ下記からご応募ください。

herp.careers