💎

Ruby の method メソッドは対象の可視性を破壊する

2021/03/16に公開

前置き

検証環境

  • Ruby (CRuby) 2.7.0

Object#method とは

https://docs.ruby-lang.org/ja/latest/method/Object/i/method.html

オブジェクトのメソッド name をオブジェクト化した Method オブジェクトを返します。

https://docs.ruby-lang.org/ja/latest/class/Method.html

(継承関係はありませんが)Method は Proc と似た存在であり、
#call メソッドを呼び出すことで、オブジェクト化されたメソッドの処理を実行することができます。

class Klass
  def value_2x(value)
    value * 2
  end
end

m = Klass.new.method(:value_2x)
m.call(1) #=> 2

Proc 同様、ブロック付きメソッドの呼び出しなどに使うことが多いかと思います。

class Klass
  def array_2x(arr)
    arr.map(&method(:value_2x))
  end

  private

  def value_2x(value)
    value * 2
  end
end

arr = [1, 2, 3]
Klass.new.array_2x(arr) #=> [2, 4, 6]

Object#method は可視性を破壊する

本題です。
この Object#method ですが、private メソッドからも Method オブジェクトを生成することができ、
生成されたオブジェクトはどこからでも処理の呼び出しが可能になります。

class Klass
  private

  def value_2x(value)
    value * 2
  end
end

klass = Klass.new
# 普通に実行する
klass.value_2x(1) #=> NoMethodError: private method `value_2x' called

# Method 化してから実行する
klass.method(:value_2x).call(1) #=> 2

この挙動が意図されたものかはわかりませんが、少なくとも今日 (2021/03/16) 現在、
Method オブジェクトには元のメソッドの可視性を外部から参照する手段が提供されていません。
https://bugs.ruby-lang.org/issues/11689

結論

Object#public_method が用意されているので、可視性の考慮が必要な場合はそちらを使いましょう。
https://docs.ruby-lang.org/ja/latest/method/Object/i/public_method.html

class Klass
  def value_2x(value)
    private_value_2x(value)
  end

  private

  def private_value_2x(value)
    value * 2
  end
end

klass = Klass.new
klass.public_method(:value_2x)         #=> #<Method: Klass#value_2x(value) (pry):2>
klass.public_method(:private_value_2x) #=> NameError: method `private_value_2x' for class `Klass' is  private

おまけ(考察)

(あくまでも私の個人的な考えです)

  • Object#public_methodModule#public_instance_method などが定義されている
  • Method#visibility が(現状)存在しない

これらの事実から、

  • Method オブジェクトは生成できれば call できるべき
  • call できないのであればオブジェクトが生成できないべき

と考えられているのではないかと思っています。

Discussion