Есть ли способ получить доступ к аргументам метода в Ruby?

Новичок в Ruby и ROR и любовь к нему каждый день, так что вот мой вопрос, так как я не представляю, как Google это (и я пытался:))

у нас есть метод

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

Так что я ищу способ передачи всех аргументов в метод, без перечисления каждого из них. Так как это Ruby, я предполагаю, что есть способ:) если бы это был Java, я бы просто перечислил их:)

Выход будет:

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

12 ответов

Решение

В Ruby 1.9.2 и более поздних версиях вы можете использовать parameters метод на метод, чтобы получить список параметров для этого метода. Это вернет список пар с указанием имени параметра и его обязательности.

например

Если вы делаете

def foo(x, y)
end

затем

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

Вы можете использовать специальную переменную __method__ чтобы получить имя текущего метода. Таким образом, внутри метода имена его параметров могут быть получены с помощью

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

Затем вы можете отобразить имя и значение каждого параметра с помощью

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

Примечание: так как этот ответ был изначально написан, в текущих версиях Ruby eval больше нельзя вызывать с символом. Для решения этого явного to_s был добавлен при построении списка имен параметров, т.е. parameters.map { |arg| arg[1].to_s }

Начиная с Ruby 2.1, вы можете использовать binding.local_variable_get для чтения значения любой локальной переменной, включая параметры метода (аргументы). Благодаря этому вы можете улучшить принятый ответ, чтобы избежать злой Eval.

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

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

Один из способов справиться с этим:

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

Это интересный вопрос. Может быть, используя local_variables? Но должен быть способ, отличный от использования eval. Я смотрю в 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

Если вам нужны аргументы в качестве хэша, и вы не хотите загрязнять тело метода хитрым извлечением параметров, используйте это:

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

Просто добавьте этот класс в ваш проект:

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

Это может быть полезно...

  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

Если функция находится внутри какого-либо класса, вы можете сделать что-то вроде этого:

class Car
  def drive(speed)
  end
end

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

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

Вы можете определить константу, такую ​​как:

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

И используйте это в своем коде как:

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

Похоже, что то, что пытается решить этот вопрос, можно сделать с помощью только что выпущенного мной жемчужины https://github.com/ericbeland/exception_details. В нем будут перечислены локальные переменные и значения (и переменные экземпляра) из спасенных исключений. Может стоит посмотреть...

Прежде чем я пойду дальше, вы передаете слишком много аргументов в foo. Похоже, что все эти аргументы являются атрибутами в модели, верно? Вы действительно должны передавать сам объект. Конец речи.

Вы можете использовать аргумент "splat". Это пихает все в массив. Это будет выглядеть так:

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

Если вы измените сигнатуру метода, вы можете сделать что-то вроде этого:

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

Или же:

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

В этом случае интерполируется args или же opts.values будет массив, но вы можете join если на запятой. ура

Чтобы инкапсулировать его в отдельную функцию, вы можете использовать это. Он работает как для методов экземпляра, так и для методов класса, а также для включенных или унаследованных методов:

      # @param [Binding] external_binding
def get_method_arguments(external_binding)
  calling_object = external_binding.receiver

  calling_method_identifier = caller_locations(1, 1)[0].label.to_sym
  calling_method = calling_object.method(calling_method_identifier)

  args = calling_method.parameters.map do |param_info|
    param = param_info[1]
    arg = external_binding.local_variable_get(param)
    "#{param} = #{arg}"
  end
  args.join(', ')
end

Пример использования:

      require 'logger'
def do_stuff(arg1, arg2, arg3, arg4, arg5)
  Logger.new(STDOUT).debug("Method #{__method__} called with args: #{get_method_arguments(binding)}")
end
do_stuff(111, 222, 333, 444, 555)
Другие вопросы по тегам