alias_method_chain と prepend を同時に同じメソッドに適用できない問題

Rails 5.0 のリリースが近づいてきてますが、Rails 5.0 から alias_method_chain を使っていると deprecation warning が出るようになりました https://github.com/rails/rails/pull/19434 。 単純に alias_method_chainprepend に書き換えればいいかと思いきや、かなりのレアケースではあるけれども、以前実際に失敗した事例の紹介です。

#!/usr/bin/env ruby
require 'active_support/all'

module M
  def foo
    p 'M#foo'
    super
  end
end

class C
  def foo
    p 'C#foo'
  end
end

class C
  def foo_with_modified
    p 'C#foo modified'
    foo_without_modified
  end

  alias_method_chain :foo, :modified
  prepend M
end

C.new.foo

これを実行すると

"M#foo"
"C#foo modified"
"C#foo"

という出力結果が得られる。予想通り。

#!/usr/bin/env ruby
require 'active_support/all'

module M
  def foo
    p 'M#foo'
    super
  end
end

class C
  def foo
    p 'C#foo'
  end
end

class C
  def foo_with_modified
    p 'C#foo modified'
    foo_without_modified
  end

  prepend M
  alias_method_chain :foo, :modified
end

C.new.foo

これを実行すると SystemStackError。変わったのは alias_method_chainprepend の順序だけ。

言われてみればたしかにそうなるのはわかるけれども、たとえば activerecord をモンキーパッチで拡張するような gem が複数あった場合、たまたま同じメソッドに対する alias_method_chainprepend が混在していると、初期化順でエラーになったりならなかったりする。