How to Access Method Arguments in Ruby

Is there a way to access method arguments in Ruby?

In Ruby 1.9.2 and later you can use the parameters method on a method to get the list of parameters for that method. This will return a list of pairs indicating the name of the parameter and whether it is required.

e.g.

If you do

def foo(x, y)
end

then

method(:foo).parameters # => [[:req, :x], [:req, :y]]

You can use the special variable __method__ to get the name of the current method. So within a method the names of its parameters can be obtained via

args = method(__method__).parameters.map { |arg| arg[1].to_s }

You could then display the name and value of each parameter with

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')

Note: since this answer was originally written, in current versions of Ruby eval can no longer be called with a symbol. To address this, an explicit to_s has been added when building the list of parameter names i.e. parameters.map { |arg| arg[1].to_s }

How to get a list of the arguments a method is called with

You can always define a method that takes an arbitrary number of arguments:

def foo(*args)
puts args.inspect
end

This does exactly what you want, but only works on methods defined in such a manner.

The *args notation means "zero or more arguments" in this context. The opposite of this is the splat operator which expands them back into a list, useful for calling other methods.

As a note, the *-optional arguments must come last in the list of arguments.

Ruby - get method params names and values

Yup! In ruby, it's called the binding, which is an object that encapsulates the context in which a particular line runs. The full docs are here, but in the case of what you're trying to do...

def my_method(arg1, arg2)
var = arg2
p binding.local_variables #=> [:arg1, :arg2, :var]
p binding.local_variable_get(:arg1) #=> 1
p Hash[binding.local_variables.map{|x| [x, binding.local_variable_get(x)]}] #=> {:arg1 => 1, :arg2 => 2, :var => 2}
end

my_method(1, 2)

I'd strongly advise against Binding#eval, if you can possibly help it. There's almost always a better way to sovle problems than by using eval. Be aware that binding encapsulates context on the line at which it is called, so if you were hoping to have a simple log_parameters_at_this_point method, you'll either need to pass the binding into that method, or use something cleverer like binding_of_caller

Access `self` of an object through the parameters

Not recommended, but instance_eval would somehow work:

[1, 2, 3, 4].instance_eval { at(rand(size)) }

And you can also break out of tap:

[1, 2, 3, 4].tap { |a| break a.at(rand(a.size)) }

There's an open feature request to add a method that yields self and returns the block's result. If that makes it into Ruby, you could write:

[1, 2, 3, 4].insert_method_name_here { |a| a.at(rand(a.size)) }

How to get argument names using reflection

I suggest you take a look at Merb's action-args library.

require 'rubygems'
require 'merb'

include GetArgs

def foo(bar, zed=42)
end

method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]]

If you don't want to depend on Merb, you can choose and pick the best parts from the source code in github.

Get Method Arguments using Ruby's TracePoint

As you point in your question and as it documented in ruby documentation, tp.self returns a traced object, which have a method method you are looking for.
I think you should use

method = tp.self.method(tp.method_id)

instead of

method = eval("method(:#{tp.method_id})", tp.binding)

UPDATE. Some explanation regarding your last paragraph in question. tp.self in first case (when you call foo) is point to main, because you define foo method in main context and it points to String object in second case because sub is defined there. But tp.binding.eval("self") returns main in both cases because it returns a calling context (not a 'define' context as you expect) and in both cases it is main.

UPDATE (in reply to comment) I think that the only way to do this is to monkey patch sub and all other methods that you are interesting for. Code example:

class String
alias_method :old_sub, :sub
def sub(*args, &block)
old_sub(*args, &block)
end
end

trace = TracePoint.trace(:call, :c_call) do |tp|
tp.disable
case tp.method_id
when :sub
method = tp.self.method(tp.method_id)
puts method.parameters.inspect
end
tp.enable
end

trace.enable

"foo".sub(/(f)/) { |s| s.upcase }

One big drawback is that you can't use $1, $2, ... vars in your original blocks. As pointed here where is no way to make it works. However you can still use block parameters (s in my example).



Related Topics



Leave a reply



Submit