It seems that only way we can inspect the values of method arguments is by having access to binding
of method. Using Tracepoint
class, we can get hold of such a binding object and then inspect the values of all optional
parameters.
We need to ensure that we invoke the desired method with only required parameters, so that default parameters gets assigned their default values.
Below is my attempt to do this - it works with both instance methods and class methods. In order to invoke instance methods, we need to instantiate the class - if the constructor requires parameters, then, it can get tricky to create an object. To circumvent that issue, this code dynamically creates a sub-class of given class and defines a no-arg constructor for it.
class MyClass
# one arg constructor to make life complex
def initialize param
end
def index(arg1, arg2="hello", arg3 = 1, arg4 = {a:1}, arg5 = [1,2,3])
raise "Hi" # for testing purpose
end
def self.hi(arg6, arg7="default param")
end
end
def opt_values(clazz, meth)
captured_binding = nil
TracePoint.new(:call) do |tp|
captured_binding = tp.binding
end.enable {
# Dummy sub-class so that we can create instances with no-arg constructor
obj = Class.new(clazz) do
def initialize
end
end.new
# Check if it's a class method
meth_obj = clazz.method(meth) rescue nil
# If not, may be an instance method.
meth_obj = obj.method(meth) rescue nil if not meth_obj
if meth_obj
params = meth_obj.parameters
optional_params = params.collect {|i| i.last if i.first == :opt}.compact
dummy_required_params = [""] * (params.size - optional_params.size)
# Invoke the method, and handle any errors raise
meth_obj.call *dummy_required_params rescue nil
# Create a hash for storing optional argument name and its default value
optional_params.each_with_object({}) do |i, hash|
hash[i] = captured_binding.local_variable_get(i)
end
end
}
end
p opt_values MyClass, :index
#=> {:arg2=>"hello", :arg3=>1, :arg4=>{:a=>1}, :arg5=>[1, 2, 3]}
p opt_values MyClass, :hi
#=> {:arg7=>"default param"}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…