【一人 Ruby Advent Calendar 2017】private メソッドを呼びだそう【10日目】

一人 Ruby Advent Calendar 2017 10日目の記事になります。
さて、今回は Rubyprivate についていろいろ

Ruby で private メソッドを定義する

Ruby では private(という名前のメソッド)を使用することでメソッドを private として定義することができます。

class X
    def homu
        "homu"
    end
    # 任意のメソッドを private にする
    private :homu

    # 引数がなかった場合は以降に定義されるメソッドを private メソッドとして定義する
    private
    def mami
        # メソッド内であれば呼び出すことが出来る
        homu + mado
    end

    def mado
        "mado"
    end
end

x = X.new

# private メソッドは呼び出せない
# Error: private method `homu' called for #<X:0x00000000025cc1a8> (NoMethodError)
p x.homu

こんな感じでクラス外からはメソッドを呼び出すことができません。

private メソッドを呼び出す

さてさて、private メソッドを定義することでクラス外から呼び出せない…と、思いきや実はクラス外からも簡単に呼び出すことができます。

#send を使う

class X
    private
    def homu
        "homu"
    end
end

x = X.new

# send だと任意のメソッドで呼び出すことが出来る
p x.send :homu
# => "homu"

#instance_exec#instance_eval を使う

class X
    private
    def homu
        "homu"
    end
end

x = X.new

p x.instance_eval {
    # ブロック内で直接メソッド呼び出す
    homu
}
# => "homu"

特異メソッド経由で呼び出す

class X
    private
    def homu
        "homu"
    end
end

x = X.new

# メソッドを再定義して
def x.homu
    # 元のメソッドを呼び出す
    super()
end
p x.homu

どういうこと?

これは Rubyprivate の仕様がx.func のように『x. をつけて呼び出せないようにする』となっているためです。
なので

x = X.new
# インスタンスオブジェクトからメソッド呼び出す
x.func

のように通常はインスタンスオブジェクト経由で呼び出す事ができません。

しかし、逆にいえば x.func のような形式ではなくて

# #send 経由で呼び出す
x.send :func

のように #send 経由でメソッド呼び出したり、

# instance_eval 内ではコンテキストが x になるので
# x. を付ける必要なく func を呼び出せる
x.instance_eval { func }

みたいに #instance_exec を使うことで x. を付けることなくメソッドを呼び出す事ができます。

ちなみに次のように self. を付けて呼び出した場合はエラーになります。

class X
    def homu
        # OK
        homu

        # Error: private method `mado' called for #<X:0x0000000001934670> (NoMethodError)
        self.homu
    end

    private
    def mado
        "mado"
    end
end

まとめ

  • private メソッドにメソッド名を渡すとそのメソッドが private メソッドになる
  • private メソッドに引数を渡さなかった場合は以降に定義されるメソッドが private になる
  • private メソッドは x.func のように x. をつけて呼び出せない
  • x. をつけなければ呼び出せるので簡単に外部から呼び出すことが出来る


このように Ruby では『private メソッドが外部から呼び出せない』とは限らないので広い意味での『private メソッド』を定義したい場合は工夫が必要になってきます。