I have the symbol name of a method that I'd like to call with some arguments. What I'm really trying to do boils down to this code snippet:
method.to_proc.call(method)
In this case, method
is the symbol name of a method on the object. In my case, I'm trying to call a method that happens to be private on the object.
This is the error output that I get:
>$ 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>'
Here's a self-contained example that demonstrates this behavior:
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
The important bit is in the block marked #tests
. The data
variable is a set of inputs and expected results. The test_value_*
methods are private methods that are the tests to run.
I've tried public_send(method, value)
and method.to_proc.call(value)
, but both result in the private method
error.
What would be the right way to call a private method named as a symbol in this case? I'm looking for both an explanation and a syntactically correct answer.
After a fair amount of searching, I found an alternative answer than Object#send
, that has an unanticipated feature benefit. The solution is to use the Object#method
to return a Method
object for the symbol name.
A Method
object is a Proc
-like callable object, so it implements the #call
interface, which fits the bill nicely. Object
has many such useful helpers defined in its interface.
In context of the original question, this is how it works:
#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
The important bits are:
self.method(method).call(value)
This will convert the symbol name to a Method
object, and then invoke the method with value
supplied as the parameter. This works roughly equivalently to the send
method solution, in functional terms. However, there are some differences to note.
send
is going to be somewhat more efficient, as there's no overhead in the conversion to a Method
. Method#call
and send
use different internal calling mechanisms, and it appears that send
has less call overhead, as well.
The unanticipated feature of using Object#method
is that the Method
object is easily converted to a Proc
object (using Method#to_proc
). As such, it can be stored and passed as a first-class object. This means that it can be supplied in place of a block or provided as a callback, making it useful for implementing flexible dispatch solutions.