おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

savanna.io をRails 7.2にアップグレードした

お仕事SNSsavanna.ioの開発を空いた時間でやっています。先日Rails 7.2のbetaが出たので試しにCIを回してみたらそれほど問題なくアップグレードできそうなのでサッと対応してアップグレードしました。以下対応した内容を書いています。

bulletを外す

bulletの依存でbundle installができなかったので、一旦bulletを外しました。 PRは出ているので取り込まれたら戻すのを検討します。

Support active record 7.2.0 by hatsu38 · Pull Request #707 · flyerhzm/bullet

enumの形式変更に対応

Rails7.0からenumの引数の渡し方が新しくなりました。7.2からは古い渡し方はdeprecatedになり、8.0からは新しい方式だけになる模様です。

# 旧
enum status: { unlooked: 0, looked: 10, read: 20 }
# 新
enum :status, unlooked: 0, looked: 10, read: 20

Allow new syntax for enum to avoid leading _ from reserved options by kamipo · Pull Request #41328 · rails/rails

savanna.ioではconfig/environments/test.rbで次のようにしてdeprecation warningのときに例外を発生させるようにしているので、Rails7.2にアップグレードしたことでCIが失敗しました。

config.active_support.deprecation = :raise

Rails7.2にアップグレードする際に必須の修正ではないですが、早く対応するに越したことはないので一緒に修正しました。

prepared statementの実行でエラー

Rails7.2にアップグレードすると、次のように文字列でクエリを渡している箇所でActiveRecord::StatementInvalidエラーになりました(コードは簡略化しています)

def not_todays_target
  User.joins(:conditions).where(
    'conditions.transfer_on - interval :duration = :today',
    today: Time.zone.today,
    duration:
  )
end

コードを追いかけて調査したところ、Rails7.1まで上記のクエリはprepared statementの対象外だったのがRails7.2から対象になったことが原因のようでした。intervalリテラルの部分をプレースホルダとして利用することはできないらしいので、クエリの書き方が悪いと判断してdurationの部分を文字列展開させることで対応しました*1

def not_todays_target
  User.joins(:conditions).where(
    'conditions.transfer_on - interval #{duration} = :today',
    today: Time.zone.today,
    duration:
  )
end

prepared statementの対象が変更になったのはこのPRが原因のようです。

Relation#where build BoundSqlLiteral rather than eagerly interpolate · rails/rails@8e6a5de

このPRは、DBのconnectionを使う機会をなるべく減らす一環で作られたものです。

これまでは例えばwhereメソッドの実行時にActiveRecord::Base.sanitize_sqlを利用してプレースホルダの置換を行っていました。このとき、利用するDBによるクォートの付け方の差異をconnectionを利用することで解決しています。このPRではプレースホルダの置換、つまりconnectionの利用を遅延させて、prepared statementを利用する設定であればそれを利用するようにコードを修正しています。結果として、これまでprepared statementを利用していなかったクエリが、prepared statementの対象になっています。

所感

思っていたよりも簡単にアップグレードできました。次はconfig.load_defaults 7.2にチャレンジしようと思います。

*1:durationはユーザ入力文字列ではないのでセキュリティの問題はありません