メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Rails8.0にアップグレードしたらinsert_allの振る舞いが変わった

こんにちは。メドピアのお手伝いをしている@willnetです。最近寒くなってきましたね。毎年この時期に風邪をひくので最近は手洗いうがいを怠らないようにしています。このたび久しぶりにテックブログに寄稿します。

insert_allの仕様変更に関するPR

メドピア内のとあるRailsアプリケーションのバージョンを7.2から8.0にアップグレードしたときにinsert_allの仕様変更に気づいた話をします。 insert_allの仕様変更に該当するのは次のPRです。

Fix active record insert values of type cast and serialize by OuYangJinTing · Pull Request #48139 · rails/rails

上記PRではsaveメソッドとinsert_all(及びinsertinsert_all!)メソッドで値の変換方法が一致していないのを修正しています。PRではString型のカラムにArrayやHashをアサインして変更内容を検証していますが、この変更でDateTime型やTime型のカラムの振る舞いも変わることがわかりました。

変更内容

前提として、次のようにRailsのタイムゾーンを"Tokyo"にしています。

class TestApp < Rails::Application  
  config.time_zone = "Tokyo"
end

そのうえで次のようにstart_timeカラム(Time型)、start_atカラム(DateTime型)に値をいれます。どちらも文字列を利用しているのがポイントです。

Post.insert_all([{ start_time: "12:00:00", start_at: "2025-10-29 12:00:00" }])

するとRails7.2では次のようなクエリが発行されます

INSERT INTO "posts" ("start_time","start_at") VALUES ('2000-01-01 12:00:00', '2025-10-29 12:00:00') ON CONFLICT  DO NOTHING RETURNING "id"

Rails8.0ではこうです。

INSERT INTO "posts" ("start_time","start_at") VALUES ('2000-01-01 03:00:00', '2025-10-29 03:00:00') ON CONFLICT  DO NOTHING RETURNING "id"

INSERTする時刻が9時間ズレてしまいました。

もともとRailsはsave時にタイムゾーン設定を考慮して、DBにはUTCに変換した時刻を入れるようになっています。しかしinsert_allを利用したときはsaveと振る舞いが違っていました。ActiveSupport::TimeWithZoneオブジェクトがアサインされたときはUTCへの変換が行われますが、文字列がアサインされたときにはタイムゾーン変換を行わずにクエリを発行しています。Rails8.0ではこの問題が解消されてinsert_allsaveが同じように振る舞います。

感想

これはバグフィックスとしては妥当な変更だと思いますが、結果として発行するクエリが変わるので、アップグレードガイドなどに変更する旨を記載するか設定で振る舞いを切り替えられるとより良かったように思えます。Rails8.0がリリースされる前にこの仕様変更に気づいてPRを出すべきだったので、やっぱり「定期的にRailsのedgeでCIを実行する」は実施すべきプラクティスだよな〜という気持ちを新たにしました。

この記事が今後Rails8.0にアップグレードする方の参考になれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp