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
How to Use Gem to Install Rails on Ubuntu
Ruby 1.9.2 and Rails 3 Cannot Open Rails Console
Ruby: Installing Rmagick on Ubuntu
Rails Sends 0 Byte Files Using Send_File
Why Doesn't My Cron Job Work Properly
Ruby on Rails - Unable to Convert "\X89" from Ascii-8Bit to Utf-8 for Xxx/Xxxx/Xxxx
Mavericks, Rbenv, Your Ruby Version Is 2.0.0, But Your Gemfile Specified 2.1.1
Using God to Monitor Unicorn - Start Exited with Non-Zero Code = 1
Why Is Sudo: Bundle Command Not Found
Execute a Sudo Command in Ruby on Rails App
How to Prompt for a Sudo Password Using Ruby
Rvm Warning! Path Is Not Properly Set Up
How to Wait for Process to Finish Using Io.Popen
Launching Background Process in Capistrano Task
Fork Child Process with Timeout and Capture Output