在 Ruby 中通过符号名称调用私有方法

Calling private methods by symbol name in Ruby

我有一个方法的符号名称,我想用一些参数调用它。我真正想做的事情归结为这个代码片段:

method.to_proc.call(method)

在这种情况下,method 是对象上方法的符号名称。在我的例子中,我试图调用一个恰好在对象上是私有的方法。

这是我得到的错误输出:

>$ ruby symbol_methods.rb 
symbol_methods.rb:33:in `call': private method `test_value_1' called for "value":String (NoMethodError)
    from symbol_methods.rb:33:in `block (2 levels) in <main>'
    from symbol_methods.rb:30:in `each'
    from symbol_methods.rb:30:in `block in <main>'
    from symbol_methods.rb:29:in `each'
    from symbol_methods.rb:29:in `<main>'

这是一个演示此行为的独立示例:

data = [
  ["value", true],
  ["any value here", true],
  ["Value", true],
]

def matches_value(string)
  string == "value"
end

def contains_value(string)
  string.gsub(/.*?value.*?/, "\1")
end

def matches_value_ignore_case(string)
  string.downcase == "value"
end

#tests
[:matches_value, :contains_value, :matches_value_ignore_case].each_with_index do |method, index|
  test = data[index]
  value = test[0]
  expected_result = test[1]
  result = method.to_proc.call(value)  # <<== HERE
  puts "#{method}: #{result == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
end

重要的位在标记为 #tests 的块中。 data 变量是一组输入和预期结果。 test_value_* 方法是私有方法,是对 运行.

的测试

我试过 public_send(method, value)method.to_proc.call(value),但两者都会导致 private method 错误。

在这种情况下调用名为符号的私有方法的正确方法是什么?我正在寻找解释和语法正确的答案。

改用send

puts "#{method}: #{send(method, value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"

经过大量搜索,我找到了一个替代答案,而不是 Object#send, that has an unanticipated feature benefit. The solution is to use the Object#method to return a Method 符号名称的对象。

一个Method对象是一个Proc-like callable object, so it implements the #call interface, which fits the bill nicely. Object在其接口中定义了许多这样有用的助手。

在原始问题的上下文中,它是这样工作的:

#tests
[:test_value_1, :test_value_2, :test_value_3].each do |method|
  data.each do |test|
    value = test[0]
    expected_result = test[1]
    puts "#{method}: #{self.method(method).call(value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
  end
end

重要的部分是:

self.method(method).call(value)

这会将符号名称转换为 Method 对象,然后使用 value 作为参数调用该方法。这在功能方面 大致等效 。但是,需要注意一些差异。

send 会更有效率,因为在转换为 Method 时没有开销。 Method#callsend 使用不同的内部调用机制,看起来 send 的调用开销也较小。

使用 Object#method 意想不到的功能 Method 对象很容易转换为 Proc 对象(使用 Method#to_proc).因此,它可以作为 first-class 对象存储和传递。这意味着它可以代替块提供或作为回调提供,从而有助于实现灵活的调度解决方案。