Productive Outs

価値ある凡打を積み重ねる

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

仕事で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での動作に関しては保証されていなかった。

github.com

原因調査

さらに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させる変更がされているのを発見。

github.com

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

discuss.rubyonrails.org

どのように解決したか

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

まとめ

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


↓「アウトプットサボってんじゃねーよ」と言われた気がした。。。がんばります。 f:id:kyouashita18:20210821013146p:plain