Edited at

public_sendを信用しちゃいけないよ、というお話

More than 5 years have passed since last update.

Rubyでは、privateなメソッドは普通に呼んでもエラーになります。

class Foo

private
def bar
puts "Don't call me!"
end
end

Foo.new.bar
#=> NoMethodError: private method `bar' called for #<Foo:0x007fe1232991c0>

一方、Rubyにはsendというメソッドがあって動的なディスパッチが可能となっていますが、これはprivateなメソッドも呼べてしまいます。

Foo.new.send :bar

#=> Don't call me!

そこで、publicなメソッドのみが呼べるpublic_sendというものが用意されています。

Foo.new.public_send :bar

#=> NoMethodError: private method `bar' called for #<Foo:0x007fe123238aa0>

しかし、public_sendにはこんな抜け道が……

Foo.new.public_send :instance_eval, 'bar'

#=> Don't call me!

そう、instance_evalを使い、引数として呼びたいメソッドの名前を「文字列で」渡せばprivateなメソッドも呼べてしまうのです(シンボルでは動作しません)。

この辺はRubyの自由度の高さですが、それが裏目に出る場合もあります。

例えばRailsで外部からの入力を直接、動的にディスパッチする必要がある場合、public_sendを使うだけでは危険です。おそらく呼びたいメソッドの種類はそう多くないので、ホワイトリスト形式でそれらをリストアップし、残りはsendすべきではないでしょう(そもそもそんな危ない橋を渡るな、という話ですが……)。

whitelist = %w(foo bar buzz)

if whitelist.include? params[:method_name]
obj.public_send params[:method_name]
else
raise "Not in whitelist"
end

以上、Rubyの世界ではすべてがpublicなんだ!というお話でした。

追記:

参考:http://www.phenoelit.org/stuff/hitb2013ams/#/