Is there a way to access method arguments in Ruby?

Ruby on-RailsRuby

Ruby on-Rails Problem Overview


New to Ruby and ROR and loving it each day, so here is my question since I have not idea how to google it (and I have tried :) )

we have method

def foo(first_name, last_name, age, sex, is_plumber)
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"    
end

So what I am looking for way to get all arguments passed to method, without listing each one. Since this is Ruby I assume there is a way :) if it was java I would just list them :)

Output would be:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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 }

Solution 2 - Ruby on-Rails

Since Ruby 2.1 you can use binding.local_variable_get to read value of any local variable, including method parameters (arguments). Thanks to that you can improve the accepted answer to avoid evil eval.

def foo(x, y)
  method(__method__).parameters.map do |_, name|
    binding.local_variable_get(name)
  end
end

foo(1, 2)  # => 1, 2

Solution 3 - Ruby on-Rails

One way to handle this is:

def foo(*args)
    first_name, last_name, age, sex, is_plumber = *args
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{args.inspect}"    
end

Solution 4 - Ruby on-Rails

This is an interesting question. Maybe using local_variables? But there must be a way other than using eval. I'm looking in Kernel doc

class Test
  def method(first, last)
    local_variables.each do |var|
      puts eval var.to_s
    end
  end
end

Test.new().method("aaa", 1) # outputs "aaa", 1

Solution 5 - Ruby on-Rails

This may be helpful...

  def foo(x, y)
    args(binding)
  end

  def args(callers_binding)
    callers_name = caller[0][/`.*'/][1..-2]
    parameters = method(callers_name).parameters
    parameters.map { |_, arg_name|
      callers_binding.local_variable_get(arg_name)
    }    
  end

Solution 6 - Ruby on-Rails

If you need arguments as a Hash, and you don't want to pollute method's body with tricky extraction of parameters, use this:

def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
  args = MethodArguments.(binding) # All arguments are in `args` hash now
  ...
end

Just add this class to your project:

class MethodArguments
  def self.call(ext_binding)
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
    method_name = ext_binding.eval("__method__")
    ext_binding.receiver.method(method_name).parameters.map do |_, name|
      [name, ext_binding.local_variable_get(name)]
    end.to_h
  end
end

Solution 7 - Ruby on-Rails

You can define a constant such as:

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"

And use it in your code like:

args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)

Solution 8 - Ruby on-Rails

Before I go further, you're passing too many arguments into foo. It looks like all of those arguments are attributes on a Model, correct? You should really be passing the object itself. End of speech.

You could use a "splat" argument. It shoves everything into an array. It would look like:

def foo(*bar)
  ...
  log.error "Error with arguments #{bar.joins(', ')}"
end

Solution 9 - Ruby on-Rails

If you would change the method signature, you can do something like this:

def foo(*args)
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{args}"    
end

Or:

def foo(opts={})
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{opts.values}"    
end

In this case, interpolated args or opts.values will be an array, but you can join if on comma. Cheers

Solution 10 - Ruby on-Rails

It seems like what this question is trying to accomplish could be done with a gem I just released, https://github.com/ericbeland/exception_details. It will list local variables and vlaues (and instance variables) from rescued exceptions. Might be worth a look...

Solution 11 - Ruby on-Rails

If the function is inside some class then you can do something like this:

class Car
  def drive(speed)
  end
end

car = Car.new
method = car.method(:drive)

p method.parameters #=> [[:req, :speed]] 

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionHaris KrajinaView Question on Stackoverflow
Solution 1 - Ruby on-RailsmikejView Answer on Stackoverflow
Solution 2 - Ruby on-RailsJakub JirutkaView Answer on Stackoverflow
Solution 3 - Ruby on-RailsArun Kumar ArjunanView Answer on Stackoverflow
Solution 4 - Ruby on-RailsRaffaeleView Answer on Stackoverflow
Solution 5 - Ruby on-RailsJon JaggerView Answer on Stackoverflow
Solution 6 - Ruby on-RailsgreenbackView Answer on Stackoverflow
Solution 7 - Ruby on-RailsAl JohriView Answer on Stackoverflow
Solution 8 - Ruby on-RailsTom LView Answer on Stackoverflow
Solution 9 - Ruby on-RailsSimon BagreevView Answer on Stackoverflow
Solution 10 - Ruby on-RailsebelandView Answer on Stackoverflow
Solution 11 - Ruby on-RailsNikhil WaghView Answer on Stackoverflow