結論から言うと。
- 仮引数を
foo = foo
のように記述した場合の挙動が Ruby 2.2 で変わった (修正された) -
foo = foo()
またはfoo = self.foo
にすれば 2.1 と同じ挙動になる - 実行中
warning: circular argument reference
という警告メッセージが出たら、たぶんまともに動かないので注意 - Rails 4.1.2 未満は Ruby 2.2 に対応していない
経緯
Rails 3 のアプリケーションを Ruby 2.2.0 でテストしたら、謎の例外が起きた。
Failure/Error: Unable to find matching line from backtrace
TypeError:
wrong argument type nil (expected Symbol)
例外が起きる前に下記のような警告が表示されている。
.../lib/active_record/associations/has_many_association.rb:79: warning: circular argument reference - reflection
該当箇所の Rails 3.2.21 のコードをみるとこんな感じ。
def inverse_updates_counter_cache?(reflection = reflection)
...
end
Rails 4.2.0 だとこうなってる。
def inverse_updates_counter_cache?(reflection = reflection())
...
end
debugger でみてみると、Rails 3.2.21 の書き方では reflection は nil になってしまっている (reflection メソッドは attr_reader :reflection
で定義されており、@reflection
にはオブジェクトがある)。
どうも、引数省略時の値を仮引数と同名のメソッドで設定しようとしている場合に、メソッドを呼び出すのではなく、ローカル変数を参照するように変わったらしい。それで、reflection()
と明示的にメソッド呼び出しの書式にすることで、問題を回避しているものと思われる。
blame を見てみると、次のコミットで修正されたようだ。
ここから、bugs.ruby-lang.org を参照しているので見てみる。
元の挙動は bug であるとされている。確かに例に挙げられているとおり、通常は foo = foo
としたら右辺はローカル変数の参照になるので納得できる。
Yes. It was a bug.
An assignment creates a variable and it hides same name method in its RHS, as you can't call foo method inside bar method:def foo; 'foo'; end def bar foo = foo # nil end
また、同じコメントで、()
を後置することで、メソッド呼び出しであることを明示する方法が紹介されている。
Always you can call the method explicitly with ().
https://bugs.ruby-lang.org/issues/9593#note-5
文法上メソッド呼び出しであることが明示されればよいので、self.reflection
のように self.
を前置してもよい。
あとで気付いたが、2.2.0 リリース時の NEWS でも言及されていた。