📑

Rails6.1にアップグレードしたらwice_gridでFrozenErrorが出るようになった話

2023/04/23に公開

仕事でRailsのアップグレードを進めていたときに出会ったエラーの内容をゆるくまとめておく。

Rails6.1+wice_gridでFrozenErrorが発生

Rails6.0からRails6.1へのアップグレードを行った際に動作確認をしていると、ソートや検索機能を備えたテーブルを表示するページでエラーが発生した。
エラー内容は
FrozenError (can't modify frozen ActiveRecord::ConnectionAdapters::PostgreSQL::Column ~)
で、バックトレースを確認するとどうやらwice_gridというgemの内部でエラーが発生していることが分かった。

wice_gridはテーブルにソートやフィルタリング機能などを簡単に付けられるgemで、あまり頻繁に更新はされていないのでRails6.1での動作に関しては保証されていなかった。

https://github.com/kreintjes/wice_grid/tree/fix/all:embed:cite

原因調査

さらにgem内部を見ていくと、wice_gridのtable_column_matrix.rbの以下のコードで問題が発生していることが分かった。
エラーが発生していた行は4行目のself[model].each_value { |c| c.model = model }の部分。
エラーメッセージに書かれている通り、freezeによって変更が禁止されているActiveRecord::ConnectionAdapters::PostgreSQL::Columnに対して、新たな属性を追加しようとしてエラーが起きていた。

def init_columns_of_table(model) #:nodoc:
  self[model] = HashWithIndifferentAccess.new(model.columns.index_by(&:name))
  @by_table_names[model.table_name] = self[model]
  self[model].each_value { |c| c.model = model }
end

Rails6.0と6.1で何が変わったのか

上のコードはRails6.0では問題なく動いていたため、Rails6.1でActiveRecordの何かしらの仕様変更があった可能性を疑った。それぞれの環境のRails consoleで確認をしてみると、以下のようにRails6.0ではModel.columnsで返ってくる要素はfreezeされていないのに対して、Rails6.1からは要素がfreezeされるように変わっていたということが判明。

Rails6.0

Model.columns.first.frozen?
=> false

Rails6.1

Model.columns.first.frozen?
=> true

Rails6.1のコミットを追ってみると、2020年5月に以下のコミットでcolumnsメソッドで返る要素をfreezeさせる変更がされているのを発見。

[https://github.com/rails/rails/commit/c9c293e19464c9ce37316f2d917ac123d7b654e6#diff-eee8446909a6221877323c7b605b430eaf515d8e66aa27ae62de2e7614014c95L359-R359:embed:cite]

発端は以下のディスカッションで、「Model.column_namesがコピーではなく参照を返しているので"!"を付けたほうが良くない?」→「ほんならきちんとfreezeするようにしといたほうがええやろ!PR作っといたで!」みたいな流れの中で上のPRが出されたっぽい。

[https://discuss.rubyonrails.org/t/potpourri-of-head-scratchers/75459:embed:cite]

どのように解決したか

この記事ではwice_gridの内部のコードにあまり言及していないため詳細は省くが、ActiveRecord::ConnectionAdapters::PostgreSQL::Columnに新たな属性を追加せずとも必要なデータの受け渡しを行う方法が他にもあったため、その変更を加えるモンキーパッチを当てて対処した。

まとめ

今回のエラーで原因の特定に苦労した理由として、Railsのアップグレードガイドやリリースノート内に上記の仕様変更が書かれていなかった点が大きかった。おそらく「推奨されない書き方が禁止されるようになった」という話なので特にリリースノートに書くまでもないと判断されたのだろうと勝手に思っている。このエラーのおかげでgem内部のコードをしっかり読んだり、モンキーパッチの作成をする機会ができたので良かった。厄介なエラーも時が経つと良い思い出になるのかもしれない…。

(はてブから再掲)
https://kyo18.hatenablog.com/entry/2021/08/21/013452?_ga=2.94283869.1568276795.1682257158-767954213.1682003248

Discussion