zenet_logo

-株式会社ゼネット技術ブログ-

Rails 8.1 で schema.rb のカラム定義順が変わった件と CSV 列順ズレ問題の対応

はじめに

ゼネットの土屋です。
Rails 8.1 が正式リリースされてから 2 か月ほど経ち、
実際に運用してみると様々な挙動の変化が見えてきました。

今回はその中でも、Rails 8.1 の変更によって実際に発生した
CSV 出力の列順が変わってしまう問題について紹介します。


db/schema.rb のカラム定義が アルファベット順 に並ぶようになった

この仕様変更は以下のコミットで導入されました:

github.com

何が起こるのか?

例えば users テーブルが次のように定義されていたとします:

従来(Rails 8.0 まで)

id
name
age
created_at
updated_at

Rails 8.1 以降

id
age
created_at
name
updated_at

ポイントは以下の2つです:

  • id は常に先頭に来る
  • id 以外のカラムがアルファベット順にソートされる

CSV 出力で問題が発生

以下のように select("*")(または select 省略)で CSV を生成しているコードは注意が必要です。

users = User.all

CSV.generate do |csv|
  users.each do |user|
    csv << user.attributes.values  # ← カラム順に依存
  end
end

このコードは、ActiveRecord が返す カラムの並び順に依存します。

Rails 8.1 にアップデートすると、

  • 列の順番が変わる
  • 社内ツールで読み込めなくなる
  • 外部システム向け CSV のフォーマットが破壊される

といった実害が発生します。


対応策:reselect で従来の順番を維持する

Rails には、既存コードの動作を維持するための reselect メソッドが用意されています。

railsdoc.com


実装例

既存コードへの影響を最小限にするため、 カラム順を 1 行追加するだけで明示的に固定する方法を採りました。

# 旧来の出力順を明示(id は必ず先頭)
USER_COLUMN_ORDER = %w[
  id
  name
  age
  created_at
  updated_at
].freeze

users = User.all.reselect(USER_COLUMN_ORDER) # ← この1行のみ追加

CSV.generate do |csv|
  users.each do |user|
    csv << user.attributes.values
  end
end

この対応により、

  • Rails 8.1 の schema.rb の並び順に依存しない
  • 従来の CSV 列順をそのまま維持できる
  • 既存コードの変更が最小限ですむ

というメリットがあります。


ベストプラクティス

  1. select("*") を使わない
  2. CSV のように 順番が仕様になる処理では必ずカラムを明示する
  3. レガシーコードは reselect を使って段階的に移行する

最後に

select 句を明示していれば起こらない問題ではあるものの、
意外と select("*") のまま書かれたコードは少なくありません。

schema.rb のカラム順がアルファベット順に変わるだけだから
「影響はほぼないだろう」と私も油断していたのですが、
実際には CSV 出力のように順序に依存した処理で大きく影響が出ました。

今回は reselect を使って最小限の影響で対応しましたが、
アプリケーションの性質によっては別のアプローチも十分考えられます。

Rails 8.1 へのアップグレード時には、
ぜひ CSV 出力や API レスポンスなど、
「カラム順が仕様になっている部分」を見直しておくことをおすすめします。