モジュール内の特定のメソッドだけ include する(Ruby)

Ruby のモジュールは include でメソッドを Mix-in できますが、定義したすべてのメソッドが include されてしまって、特定のものだけ include するわけにはいきません。そのくらいできそうなので何か簡単なやり方があるのかもしれません。とにかく、考えてみました。

まず、こんなメソッドを Object クラスと Module クラスに付け加えます。
add_methods.rb

class Object
  def add_methods(module_to_include, *method_names)
    method_names.each do |m|
      pr = module_to_include.method(m).to_proc
      define_method(m, &pr)
    end
  end
end

class Module
  def register_all_methods
    instance_methods.each do |m|
      module_function m
    end
  end
end

 
で、メソッドを定義するモジュールを書きます。そのとき、(必要な)すべてのメソッドを module_function で指定するか、上の register_all_methods をモジュールの最後に書きます。(すべて module_function で指定する場合は、register_all_methods メソッドを書く必要はありません。いちいち指定するのが面倒なので作りました。)

module A
  def run_f
    f
  end
  
  def run_g
    g
  end
  
  def f
    puts "f called"
  end
  
  def g
    puts "g called"
  end
  register_all_methods
end

 
Mix-in するモジュールとメソッドを add_methods() で指定します。すると指定されたメソッドだけ使えるようになります。

add_methods A, :run_f, :run_g

run_f    #=>f called
run_g    #=>g called
f        #=>`<main>': undefined local variable or method `f' for main:Object (NameError)

確かに指定されたメソッドだけ使えるようになっています。

クラスの中だとこんな風になります。

class B
  add_methods A, :run_f, :run_g
  def go
    run_f
    run_g
    f
  end
end

B.new.go

 
こんなことも可能。

c1 = Object.new
c2 = Object.new

class << c1
  add_methods A, :run_f
end

c1.run_f    #=>f called
c2.run_f    #=>`<main>': undefined method `run_f' for #<Object:***> (NoMethodError)

 
なお、これは上の例で例えばメソッド f を絶対に外から呼び出せないようにしたわけではなく、A.f などとすれば呼び出せてしまいます(まあしかし、モジュールが名前空間として使いやすくなったりとか、利点もあるのではないかと思います)。けれども、少なくとも間違えて f を呼び出す可能性はだいぶ低くなると思います。

そうそう、注意点ですが、これらと include を併用すると予期しづらい誤動作の原因になる可能性があります。まあ、そんなことをする意味はありませんが。
 

Gem化

Gem 'kaki-utils' に同梱しておきました(ver. 0.0.10)。使い方は

require 'kaki/utils/add_methods'

で Object#add_methods と Module#register_all_methods が使えます。