k-tokitoh

特異メソッドのmix-in

2019-07-16

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