お仕事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
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はユーザ入力文字列ではないのでセキュリティの問題はありません