prependされたときにクラスメソッドとインスタンスメソッドを同時に追加するパターン

Rails: includeされた時にクラスメソッドとインスタンスメソッドを同時に追加する頻出パターン のprepend版がググってもあまり見当たらなかったのでメモしておきます。

ソースコード

module M
  def self.prepended(base)
    class << base
      self.prepend(ClassMethods)
    end
  end

  module ClassMethods
    def c
      puts 'c'
    end
  end

  def a
    puts 'a'
  end
end

class C
  prepend M

  def b
    puts 'b'
  end
end

C.new.b
C.new.a
C.c

上記のうち、

  def self.prepended(base)
    class << base
      self.prepend(ClassMethods)
    end
  end

の部分は、

  def self.prepended(base)
    base.singleton_class.class_eval { self.prepend(ClassMethods) }
  end

と書くこともできます。こちらの方がスッキリして良いかもしれないですね。

解説

includeに対するextendのようなものがprependにはないので、class << base; ... endで特異クラスをオープンしてそこに定義してやる。Object#singleton_classclass_evalを使っても同様のことができる。

感想

基本的にはincludeしてActiveSupport::Concernを使ったほうがレールに乗れるぶん楽だと思いますが、どうしてもprependしなくてはいけない事情が発生した場合なんかはこんな感じで実装すると良いと思います。

パーフェクトRuby

パーフェクトRuby