Есть ли способ получить доступ к аргументам метода в 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)