Posted at

特異メソッドの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