Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
415 views
in Technique[技术] by (71.8m points)

metaprogramming - How can I inspect what is the default value for optional parameter in ruby's method?

Given a class,

class MyClass
  def index(arg1, arg2="hello")
  end
end

Is it possible to obtain the default value for arg2, via some methods like Class#instance_method, or something?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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"}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...