特異メソッドのmix-in

mix-inにおける特異メソッドの扱いがよく分かっていなかったのでメモ。

結論は以下。

  • モジュールに定義された特異メソッドはmix-inされない
  • いろいろmix-inしたい場合はincludeとextendを組み合わせる
  • 「extendedフックでinclude」、「includedフックでextend」などの工夫でまとめてmix-inできる
  • ActiveSupport::Concernを利用すると良しなにやってくれる

例として、以下のメソッドを呼び出せるようにmix-inすることを考えていく。

  • Class_a.new.i_method
  • Class_a.s_method

継承

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できる!
class Class_parent
  def i_method
    puts 'this is instance method'
  end

  def self.s_method
    puts 'this is singleton method'
  end
end

class Class_a < Class_parent
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method

includeのみ

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できない
module Module_1
  def i_method
    puts 'this is instance method'
  end

  def self.s_method
    puts 'this is singleton method'
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method 
# => NoMethodError: undefined method `s_method' for Class_a:Class

extendのみ

  • Class_a.new.i_method => できない
  • Class_a.s_method => できる!
module Module_2
  def s_method
    puts 'this is singleton method'
  end
end

class Class_a
  extend Module_2
end

Class_a.new.i_method
# => NoMethodError: undefined method `i_method' for #<Class_a:0x007f8a960357c8>
Class_a.s_method
# => this is singleton method

includeとextendをそれぞれ明示的に行う

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できる!
module Module_1
  def i_method
    puts 'this is instance method'
  end
end

module Module_2
  def s_method
    puts 'this is singleton method'
  end
end

class Class_a
  include Module_1
  extend Module_2
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method

extendするとフックでincludeも行うようにする

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できる!
module Module_2
  def s_method
    puts 'this is singleton method'
  end

  def self.extended(base)
    base.include Module_1
  end

  module Module_1
    def i_method
      puts 'this is instance method'
    end
  end
end

class Class_a
  extend Module_2
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method

includeするとフックでextendも行うようにする

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できる!
module Module_1
  def i_method
    puts 'this is instance method'
  end

  def self.included(base)
    base.extend Module_2
  end 
  
  module Module_2
    def s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method

ActiveSupport::Concernをつかう

ClassMethodsモジュール

ClassMethodsモジュール内に定義したメソッドを特異メソッドとしてmix-inしてくれる。

  • Class_a.new.i_method => できる!
  • Class_a.s_method => できる!
require 'active_support'

module Module_1
  extend ActiveSupport::Concern

  def i_method
    puts 'this is instance method'
  end
  
  module ClassMethods
    def s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method

includedブロック

includedブロック内の処理を、includeした先のクラス内で実行してくれる。 なのでここで特異メソッドを定義したり、(似たようなものだが)scopeを定義したりできる。

require 'active_support'

module Module_1
  extend ActiveSupport::Concern

  def i_method
    puts 'this is instance method'
  end

  included do
    def self.s_method
      puts 'this is singleton method'
    end
  end
end

class Class_a
  include Module_1
end

Class_a.new.i_method
# => this is instance method
Class_a.s_method
# => this is singleton method