ちなみに

火曜日の空は僕を押しつぶした。

Module#remove_methodで困った件

研究用に書いていた::Migs.describeがRSpecのdescribeと競合してしまって困ったので、

module ::Migs
  spec do
    describe ::Job do
      # テストコード
    end
  end
end

と書ける様にMigs.specを書いた時にはまった点をメモ

Migs.specの実装は以下のようにしました

module ::Migs
  class << self
    # solve duplicated describe method
    def spec
      swap_method :orig_describe, :describe
      yield
      swap_method :describe, :orig_describe
    end

    private
    def swap_method(dest, orig)
      @@dest, @@orig = dest, orig
      class << self
        alias_method @@dest, @@orig
        remove_method @@orig
      end
    end
  end
end

ところが、これで最初は動いていたのですが、本体のコードをリファクタリングしている途中でModule#remove_methodでdescribeが無いと怒られてしまいました。しばらくテストコードの方を眺めて悩んでいたのですが、冷静に考えると変更したのは本体なんだからそっちにヒントがあるだろうということで、本体を見てみると、

module ::Migs
  module ModuleMethods
    def describe
      # ...
    end
    # ...
  end
  extend ModuleMethods
end

と書いていました。気づいた方がいらっしゃると思いますが、書き換える前はextendする代わりにclass << selfで書いていました。Module#remove_methodは呼び出したモジュール内のメソッドを削除するので、この場合はMigs::ModuleMethods内でModule#remove_methodを呼ばなければいけなかったのです。

という訳で書き直したswap_methodメソッドは以下、

def swap_method(dest, orig)
  ModuleMethods.module_eval do
    alias_method dest, orig
    remove_method orig
  end
end

これで、無事にテストに通りました。(module_eval使わなくていい方法無いかな)

まとめ

Module#remove_methodは呼び出したモジュール内のメソッドしか削除しない。