web-dev-qa-db-ja.com

名前に基づいてメソッドを動的に呼び出す方法は?

名前が文字列変数に含まれているときにメソッドを動的に呼び出すにはどうすればよいですか?例えば:

class MyClass
  def foo; end
  def bar; end
end

obj = MyClass.new
str = get_data_from_user  # e.g. `gets`, `params`, DB access, etc.
str  #=> "foo"
# somehow call `foo` on `obj` using the value in `str`.

これどうやってするの?これはセキュリティ上のリスクですか?

91
user502052

やりたいことは dynamic dispatch と呼ばれます。 Rubyでは非常に簡単です。 public_send を使用するだけです:

method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name

メソッドがプライベート/保護されている場合、代わりに send を使用しますが、public_sendを優先します。

method_nameの値がユーザーに由来する場合、これは潜在的なセキュリティリスクです。脆弱性を防ぐには、実際に呼び出すことができるメソッドを検証する必要があります。例えば:

if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
  obj.send(method_name)
end
130
David

Rubyで動的ディスパッチを実現する方法は複数あり、それぞれに長所と短所があります。状況に最も適した方法を選択するように注意する必要があります。

次の表に、より一般的な手法の一部を示します。

+---------------+-----------------+-----------------+------------+------------+
|    Method     | Arbitrary Code? | Access Private? | Dangerous? | Fastest On |
+---------------+-----------------+-----------------+------------+------------+
| eval          | Yes             | No              | Yes        | TBD        |
| instance_eval | Yes             | No              | Yes        | TBD        |
| send          | No              | Yes             | Yes        | TBD        |
| public_send   | No              | No              | Yes        | TBD        |
| method        | No              | Yes             | Yes        | TBD        |
+---------------+-----------------+-----------------+------------+------------+

任意のコード

一部の手法はメソッドの呼び出しのみに制限されていますが、他の手法は基本的に何でも実行できます。任意のコードの実行を許可するメソッドは、 完全に回避されない場合、細心の注意を払って使用されます である必要があります。

プライベートにアクセス

一部の手法はパブリックメソッドのみの呼び出しに制限されていますが、他の手法はパブリックメソッドとプライベートメソッドの両方を呼び出すことができます。理想的には、要件を満たす最小の可視性でメソッドを使用するよう努力する必要があります。

:テクニックが任意のコードを実行できる場合、簡単に他の方法ではアクセスできない可能性があるプライベートメソッドにアクセスするために使用します。

危険な

テクニックが任意のコードを実行できない、またはプライベートメソッドを呼び出すことができないからといって、特にユーザーが指定した値を使用している場合、それが安全であることを意味しません。削除はパブリックメソッドです。

最速で

これらのテクニックのいくつかは、Rubyバージョン。従うべきベンチマークに応じて...


class MyClass
  def foo(*args); end

  private

  def bar(*args); end
end

obj = MyClass.new

評価する

eval('obj.foo') #=> nil
eval('obj.bar') #=> NoMethodError: private method `bar' called

# With arguments:
eval('obj.foo(:arg1, :arg2)') #=> nil
eval('obj.bar(:arg1, :arg2)') #=> NoMethodError: private method `bar' called

instance_eval

obj.instance_eval('foo') #=> nil 
obj.instance_eval('bar') #=> nil 

# With arguments:
obj.instance_eval('foo(:arg1, :arg2)') #=> nil 
obj.instance_eval('bar(:arg1, :arg2)') #=> nil 

送る

obj.send('foo') #=> nil 
obj.send('bar') #=> nil 

# With arguments:
obj.send('foo', :arg1, :arg2) #=> nil 
obj.send('bar', :arg1, :arg2) #=> nil 

public_send

obj.public_send('foo') #=> nil 
obj.public_send('bar') #=> NoMethodError: private method `bar' called

# With arguments:
obj.public_send('foo', :arg1, :arg2) #=> nil 
obj.public_send('bar', :arg1, :arg2) #=> NoMethodError: private method `bar' called

方法

obj.method('foo').call #=> nil 
obj.method('bar').call #=> nil

# With arguments:
obj.method('foo').call(:arg1, :arg2) #=> nil 
obj.method('bar').call(:arg1, :arg2) #=> nil
88
Brad Werth

あなたは本当にこれに注意したいでしょう。ユーザーデータを使用してsendを介して任意のメソッドを呼び出すと、ユーザーが必要なメソッドを実行するためのスペースを空けることができます。 sendは、メソッド名を動的に呼び出すためによく使用されますが、入力値がtrustedであり、ユーザーが操作できないことを確認してください。

ゴールデンルールは、ユーザーからの入力を決して信頼しません。

13
nzifnab

send を使用して、メソッドを動的に呼び出します。

obj.send(str)
10
sawa

respond_to?を使用してメソッドの可用性を確認できます。使用可能な場合は、sendを呼び出します。例えば:

if obj.respond_to?(str)
  obj.send(str)
end
7
RameshVel