Rails アプリケーションを構築する際に、バッチ処理や便利タスクなどで Thor (現在の最新バージョンは 1.0.1) を使っている機会があると思うのですが、Rails 5.2 から Rails 6.0 で挙動が変わっていた点とその解決方法をシェアしておきます。
対象
Thor タスクのなかで options.except(:key)
を使っているコードが対象です。
class Foo < Thor option ... def foo options.except(:key) # これ end end
言い換えると Thor で options.except
を使いその引数がシンボルでなければ、この問題はありません。
現象
options
のクラスである Thor::CoreExt::HashWithIndifferentAccess
の except
メソッドの挙動が変わっています。
Rails 5.2 まで
h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2) h.except(:foo) #=> {"bar"=>2}
Rails 6.0
h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2) h.except(:foo) #=> {"foo"=>1, "bar"=>2}
なので options.except
の引数にシンボルでキー指定しても except されていない結果が返ります。
原因
https://github.com/rails/rails/pull/35771 のパフォーマンスチューニングの影響を受けてしまっていたようです。
解決
3つ例示しておきます。
1つめは場所が限られているようであれば、options.except('key')
のように引数をシンボルから文字列にするのが分かりやすいワークアランドです。ただ upstream で予期されていないであろう挙動の影響でプロダクトコードに手入れするのがちょっとくやしい。
2つめは例えば config/initializers/thor.rb のようなファイルで以下のようなモンキーパッチを適用する手段です。
class Thor module CoreExt class HashWithIndifferentAccess def except(*keys) dup.tap do |hash| keys.each { |key| hash.delete(convert_key(key)) } end end end end end
3つめ。以下のパッチを開いているのでマージされてリリースされるまで Gemfile に github
指定するなどできます。
昨晩直して先ほど PR を開いたばかりなので、これから動きがあることを期待しています。
Rails アップグレード時などの参考にどうぞ。