Rails の module ClassMethods がやっている事

  • 62
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

Rails のコードを読んでいると、module ClassMethods って多く書かれている事が分かると思いますが、こいつが何をやっているのかを話していきます。

module ClassMethods を説明する前に、まず、以下のような Module と Class を定義します。

module ModuleA
  def self.class_method_a
    'class_method_a'
  end

  def instance_method_a
    'instance_method_a'
  end
end

class ClassA
  include ModuleA
end

そして、次に include した、インスタンスメソッドとモジュールメソッドを実行してみると

ClassA.new.instance_method_a #=> "instance_method_a"
ClassA.class_method_a        #=> NoMethodError

このように class_method_aNoMethodError になる事が分かります。
class_method_a はあくまで ModuleA の特異メソッドなので、利用することが出来ないのです。

そこで、以下のようにします。

module ModuleB
  def self.included(klass)
    klass.extend ClassMethods
  end

  module ClassMethods
    def class_method_b
      'class_method_b'
    end
  end
end

class ClassB
  include ModuleB
end

ClassB.class_method_b #=> "class_method_b"

included をオーバーライドして、ModuleBinclude する際に、ClassBClassMethodsextend しています。こうすることで、クラスメソッドとして使えるようになります。

ちなみに、class_method_bextend するために Module で囲っているだけなので、Module 名に意味はありません。ただ、慣習的に module ClassMethods とするそうです。

以上が module ClassMethods の意味です。

しかし、Rails のコードを読んでいると、included を使って ClassMethodsextend している箇所は見つかりません。

module ActiveSupport
  module Configurable
    extend ActiveSupport::Concern

    ...

    module ClassMethods
      ...

そして、代わりに ActiveSupport::Concernextend していることに気がつきます。そこで、実際にマネして Module を次のようにしてみると、

module ModuleC
  extend ActiveSupport::Concern

  module ClassMethods
    def class_method_c
      'class_method_c'
    end
  end
end

class ClassC
  include ModuleC
end

ClassC.class_method_c   #=> "class_method_c"

確かにクラスメソッドとして、使えます。

これは ActiveSupport::Concernappend_features 内で ClassMethodsextend するようにオーバーライドしているためです。(append_featuresinclude の手前で呼び出されるメソッドです)
そのため、Module に ActiveSupport::Concernextend すれば、自動的にその Module が include された際に、ClassMethodsextend します。

つまり、extend ActiveSupport::Concern としておけば、ClassMethods は全てクラスメソッドとして、使えるようになります。

ちなみに、Rails 4.2 以降は以下のように class_methods で囲みます。

module ModuleD
  extend ActiveSupport::Concern

  class_methods do
    def class_method_d
      'class_method_d'
    end
  end
end

class ClassD
  include ModuleD
end

ClassD.class_method_d   #=> "class_method_c"

便利ですね。

長々と書きましたが、最後に、誤解を恐れずに一言で結論づけると module ClassMethodsinclude した際にクラスメソッドとして、利用できるというサインです。