around_filterの:ifで指定したブロックが、その前のbefore_filterでチェインが止まってても実行されちゃう
以下のようなコードがあるとします。
class OishiController < ApplicationController
before_filter :check
before_filter :cc
around_filter :arou, :if => lambda{|c| @cc.ok?}
def tsu
render :text => "ok"
end
private
def check
# something
end
def cc
@cc = true
def @cc.ok? ; true end
end
def arou
yield
end
end
around_filterの:ifに渡すブロック内で、ひとつ前のbefore_filter :ccで定義されたインスタンス変数を使っています。(あまりいい実装ではないですけど)
before_filterのcheckメソッドは、実際にはレアケースの対応を扱っていて、ごくまれにリダイレクトをしたりします。
このコードをRails4で動かしていたときは問題なかったのですが、とある事情でRails3上で動かしたとき、ごくたまに以下の例外が発生していました。
NoMethodError (undefined method `ok?' for nil:NilClass):
app/controllers/oishi_controller.rb:4:in `_callback_around_19'
調べてみると、before_filterのcheckメソッドでリダイレクトが発生したときにこの例外が出ています。
前のbefore_filterで停止しているのだから、around_filterは呼ばれないはずです。実際、このaround_filterの:if指定をはずしてみると、around_filterのarouメソッドは呼ばれません。
しかし、フィルタチェインが止まっていても、around_filterの:ifブロックは呼ばれているようです。
Railsのコードを追ってみます。filter処理はActiveSupport::Callbacksで実装されているようです。
最終的にaround_filterが呼ばれるときは以下の部分にたどり着きます。
https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/callbacks.rb#L212
def #{name}(halted)
if #{@compiled_options} && !halted
#{@filter} do
yield self
end
else
yield self
end
end
@compiled_optionsに:ifのブロック処理が入ります。なので、前のbefore_filterがフィルタチェインを止めていても(halted = true)、:ifブロックは必ず実行されることになります。
この条件判定が逆だったらよかったのかな。