🎉

Rails 8.0 で insert_all のテストが落ちた話

に公開

はじめに

初めまして!
皆様どうもこんにちは、こんばんは、おはようございます。
エンジニアの enomoto です。

今回は既存の Rails アプリケーションをバージョンアップする際、新機能の恩恵を受けられる一方で、思わぬ互換性の問題に遭遇することもあります。

今回は、Rails 8.0 へのバージョンアップ時に遭遇した insert_all の型キャストの変更について書いていきたいと思います。

最近のお仕事

最近は Rails アプリケーションのバージョンアップに取り組んでいます。

新機能も魅力的ですが、バージョンアップで最も重要かつ大変なのは既存機能の互換性確認です。
テストを実行し、一つずつ問題を潰していく地道な作業が必要になります。

今回起きた問題

バージョンアップ作業中にinsert_allを使用しているテストが失敗しました。

具体的には、enumerize gem で定義した enum 属性に string を渡すと、期待した値が格納されない問題が発生しました。
これまで enumerize は割と柔軟に string を入れようが、integer を入れようが、シンボルを入れようがうまく値に変換してくれていました。

問題のコード例

例えば、下記のようによくある status などを enumerize で定義するコードです。
しかし、バージョン 8.0 にあげるとこの部分のテストは失敗していました。(テストあってほんま良かった!!)

class User < ApplicationRecord
  enumerize :status, in: { canceled: 0, active: 1 }
end

# Rails 7.x では動作するが、Rails 8.0 では期待通りに動かない
# (...は他の属性を省略しています)
User.insert_all([{ ..., status: 'active' }])

# Rails 8.0ではデータベースには 1 ではなく、デフォルト値(0)が格納される

問題の特徴

  • エラーが出ない: 例外は発生せず、デフォルト値(0)が格納される
  • 発見しづらい: テストがなければ気づきにくい
  • bulk operations のみ影響: insert_allでのみ発生

通常の属性代入は問題なく動作するため、最初は何が原因なのか分かりませんでした。

Rails 8.0 での変更内容

Rails 8.0 では、insert_all の型キャストとシリアライズの仕組みが変更されました。

何が変わったのか

Rails 7.2 までは、insert_all で値をデータベースに挿入する際、型の serialize メソッドが直接呼ばれていました。

https://github.com/rails/rails/pull/53511

上記の PR を確認すると、Rails 8.0 からは、新しく導入された ActiveModel::Type::SerializeCastValue を経由するようになりました。
この変更により、型のシリアライズ処理がより厳密になり、対応していない型では serialize メソッドが意図通りに呼ばれなくなる場合があります。

enumerize への影響

enumerize gem で定義した enum 属性を insert_all で使用する際、この変更の影響を受けます。

Rails 7.2 での動作:

  • User.insert_all([{ status: 'active' }]) → 正常に動作
  • DB には期待通りの整数値(例: 1)が格納される

Rails 8.0 での動作:

  • User.insert_all([{ status: 'active' }]) → 期待通りに動かない
  • DB にはデフォルト値(0)が格納される
  • エラーは発生しないため、テストがないと気づきにくい

対応方法

enumerize を使うと便利に使えるメソッドがいくつかあります。
なので、これらを使って、確実に integer で値を渡すように変更しました。

# 変更前
User.insert_all([{ ..., status: 'active' }])

# 変更後
User.insert_all([{ ..., status: User.status.find_value('active').value }])
# { ..., status: 1 }になる

enumerize 側の対応

https://github.com/brainspec/enumerize/pull/469

enumerize 側でも上記の PR がマージされており、 Rails 8.0 に対応するための修正が行われました。
この修正により、insert_all でのシンボルや文字列の受け渡しが正常に動作するようになります。

ただし、2025 年 12 月現在、この修正はまだリリースされていません。

終わりに

Rails 8.0 へのバージョンアップで、insert_all の型キャスト変更により enumerize で定義した enum 属性の挙動が変わる問題に遭遇しました。

バージョンアップ時は、直接的なエラーだけでなく、このような動作の変化にも注意が必要です。

大体の変更はテストがあれば気づけることが多いです。
なので、テストはきっちり書きましょう!
書かなくて良いメリットはほぼない!というのが個人的な意見です!

しかし、CI やテスト環境だけでは、わからないことも潜んでいることもあります。
アプリケーション外部に依存するもの(例えばS3やDatadogなどのローカルでは確認できないもの)が多ければ多いほど、ステージング環境などの本番環境に近い形での動作確認もすると、なお安心という形かと思います。

DELTAテックブログ

Discussion