Ruby

Rubyのモジュールメソッドの作り方

More than 3 years have passed since last update.

モジュールクラスのオブジェクトの特異メソッド。

モジュールメソッドと呼んで良いのか迷いますが、ここでは、モジュールメソッドと呼んでおきます。

日本語での説明は苦手なので、最初に、何がしたいのかを例として書いておきます。

また長い記事なので、結果だけを知りたい方は、下の方のまとめをご覧ください。


はじめに例

module Hoge

def self.hoge
puts "hoge"
end
end
Hoge.hoge #=> hoge

class Foo
include Hoge
extend Hoge
end
Foo.new.hoge #=> undefined method `hoge' for #<Foo:0x0000010105cf60> (NoMethodError)
Foo.hoge #=> undefined method `hoge' for Foo:Class (NoMethodError)

こんな感じで、mixinやinstance化などされたくない、直接呼ばれるためだけのメソッド。

たまに作りたいですよね?

でも、複数書くとなると、クラスメソッドよりめんどくさい


  • クラスメソッドの場合

class FooClass

class << self
def method1 ; end
def method2 ; end
end
def self.method3 ; end
end


  • モジュールメソッドの場合

module HogeModule

def self.method1 ; end
def self.method2 ; end
def self.method3 ; end
end

毎回selfが必要になってめんどくさい。

『なんかスッキリ書く方法ないの?(´・ω・`)』というのが今回の発端


書き方


それなりに見かける書き方

module Bar

extend self

def method1 ; end
def method2 ; end
end

この書き方で、『インスタンスメソッドは定義されない』とか、『module_functionの代わりになる』という記述を見かける場合もありますが、そんな事はないです。


インスタンスメソッドも定義される

module Bar

extend self

def method1 ; end
def method2 ; end
end

puts "-- instance methods"
puts Bar.instance_methods.grep(/method\d/)

puts "-- private instance methods"
puts Bar.private_instance_methods.grep(/method\d/)

puts "-- singleton methods"
puts Bar.singleton_methods.grep(/method\d/)


  • 実行結果

-- instance methods

method1
method2
-- private instance methods
-- singleton methods
method1
method2

モジュールメソッドとインスタンスメソッドが作成されます。

インスタンスメソッドを作成した後、自身にextendするからです。

コードをイメージしやすいように書き直すと以下のような感じです。

module Bar

def method1 ; end
def method2 ; end
end

module Bar
extend self
end

またパブリックなので、以下のようにincludeすれば他のクラスから呼べます

module Bar

extend self
def method ; end
end

class Foo
include Bar
end

Foo.new.method


module_functionとの違い

module_function


モジュール関数とは、プライベートメソッドであると同時に モジュールの特異メソッドでもあるようなメソッドです。

例えば Math モジュールのメソッドはすべてモジュール関数です。



  • つまり、モジュールメソッドとプライベートなインスタンスメソッドを作ります

module Bar

def method1 ; end
module_function :method1
end

puts "-- instance methods"
puts Bar.instance_methods.grep(/method\d/)

puts "-- private instance methods"
puts Bar.private_instance_methods.grep(/method\d/)

puts "-- singleton methods"
puts Bar.singleton_methods.grep(/method\d/)

class Foo
include Bar
end

Foo.new.method1


  • 実行結果

-- instance methods

-- private instance methods
method1
-- singleton methods
method1
sample.rb:19:in `<main>': private method `method1' called for #<Foo:0x00000102058338> (NoMethodError)

プライベートメソッドも作られる


モジュールメソッドの書き方

以上までのような事を踏まえて、以下のように落ち着きました。

module Bar

module ModuleMethods
def method1 ; end
def method2 ; end
end

extend ModuleMethods
end

puts "-- instance methods"
puts Bar.instance_methods.grep(/method\d/)

puts "-- private instance methods"
puts Bar.private_instance_methods.grep(/method\d/)

puts "-- singleton methods"
puts Bar.singleton_methods.grep(/method\d/)


  • 実行結果

$ ruby sample.rb

-- instance methods
-- private instance methods
-- singleton methods
method1
method2

はい。

モジュールメソッドだけしか居ませんね。目標達成です。


まとめ

module Hoge

module ModuleMethods
def method1 ; end
def method2 ; end
end

extend ModuleMethods
end


  • moduleの中でmoduleを作成して、extendする

  • module_functionやextend selfはちゃんと使い分ける

  • アドバイスを頂きましたが、class << selfを使っても書けるようです


    • 下記「追記(2015/1/16)」の項に記載しました



いや〜。

今日も不毛に悩みましたね!!(^q^

ご指摘・アドバイスお待ちしております〜<(_ _)>


追記(2015/1/16):アドバイスをいただきました!

hilohiroさんにコメントで教えていただきました。


モジュールもClassクラスのインスタンスは持つので、クラスメソッドを定義するときと同じように書けると思います。


言われてみればそうですね。

気が付きませんでした。

確かにModuleもClassクラスのインスタンスですので、特異クラスとしてオープン出来る気がします!!

という事で、実際にやってみましょう。


やってみた

module Bar

class << self
def method1 ; end
def method2 ; end
end
end

puts "-- instance methods"
puts Bar.instance_methods.grep(/method\d/)

puts "-- private instance methods"
puts Bar.private_instance_methods.grep(/method\d/)

puts "-- singleton methods"
puts Bar.singleton_methods.grep(/method\d/)


  • 実行結果

$ ruby test.rb 

-- instance methods
-- private instance methods
-- singleton methods
method1
method2

得たい結果となっていますね。

こちらの方がスッキリしていると思います。


おまけ


「include 俺」でクラスメソッド

モジュールをインクルードしてクラスメソッドにしたい時

module Bar

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

module ClassMethods
def bar
puts "bar"
end
end
end

class Foo
include Bar
end

Foo.bar #=> bar

よく書かれるイディオムとはいえ、漂うやりたい放題感w