如何根据名称动态调用方法?

时间:2011-03-18 08:34:27

标签: ruby dynamic metaprogramming

如果方法名称包含在字符串变量中,如何动态调用该方法?例如:

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`.

我该怎么做?这样做是否存在安全隐患?

5 个答案:

答案 0 :(得分:127)

您要做的是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

答案 1 :(得分:82)

在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        |
+---------------+-----------------+-----------------+------------+------------+

任意代码

某些技术仅限于调用方法,而其他技术基本上只能执行任何操作。允许执行任意代码的方法应该是used with extreme caution, if not avoided altogether

Access Private

某些技术仅限于调用公共方法,而其他技术可以同时调用公共方法和私有方法。理想情况下,您应该努力使用满足您要求的最低可见度的方法。

注意 :如果某项技术可以执行任意代码,则可以轻松地使用它来访问它可能无法访问的私有方法。

危险

仅仅因为某项技术无法执行任意代码或调用私有方法并不意味着它是安全的,特别是如果您使用的是用户提供的值。删除是一种公共方法。

最快

根据您的Ruby版本,其中一些技术可能比其他技术更高效。要遵循的基准......


实施例

class MyClass
  def foo(*args); end

  private

  def bar(*args); end
end

obj = MyClass.new

EVAL

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

答案 2 :(得分:13)

真的要小心这个。使用用户数据通过send调用任何方法可以为用户打开空间以执行他们想要的任何方法。 send通常用于动态调用方法名称,但要确保输入值信任且用户无法操纵。

黄金法则永远不会信任来自用户的任何输入。

答案 3 :(得分:10)

使用send动态调用方法:

obj.send(str)

答案 4 :(得分:7)

您可以使用respond_to?检查方法的可用性。如果它可用,则拨打send。例如:

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