こんにちは。メドピアのお手伝いをしている@willnetです。最近寒くなってきましたね。毎年この時期に風邪をひくので最近は手洗いうがいを怠らないようにしています。このたび久しぶりにテックブログに寄稿します。
insert_allの仕様変更に関するPR
メドピア内のとあるRailsアプリケーションのバージョンを7.2から8.0にアップグレードしたときにinsert_allの仕様変更に気づいた話をします。 insert_allの仕様変更に該当するのは次のPRです。
上記PRではsaveメソッドとinsert_all(及びinsert、insert_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_allとsaveが同じように振る舞います。
感想
これはバグフィックスとしては妥当な変更だと思いますが、結果として発行するクエリが変わるので、アップグレードガイドなどに変更する旨を記載するか設定で振る舞いを切り替えられるとより良かったように思えます。Rails8.0がリリースされる前にこの仕様変更に気づいてPRを出すべきだったので、やっぱり「定期的にRailsのedgeでCIを実行する」は実施すべきプラクティスだよな〜という気持ちを新たにしました。
この記事が今後Rails8.0にアップグレードする方の参考になれば幸いです。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!
■募集ポジションはこちら medpeer.co.jp
■エンジニア紹介ページはこちら engineer.medpeer.co.jp
■メドピア公式YouTube www.youtube.com
■メドピア公式note
style.medpeer.co.jp