Ruby - Аргументы ключевого слова. Можете ли вы рассматривать все аргументы ключевого слова как хэш? Как?
У меня есть метод, который выглядит так:
def method(:name => nil, :color => nil, shoe_size => nil)
SomeOtherObject.some_other_method(THE HASH THAT THOSE KEYWORD ARGUMENTS WOULD MAKE)
end
Для любого данного вызова я могу принять любую комбинацию дополнительных значений. Мне нравятся именованные аргументы, потому что я могу просто посмотреть на подпись метода, чтобы узнать, какие опции доступны.
Я не знаю, есть ли ярлык для того, что я описал прописными буквами в приведенном выше примере кода.
В прежние времена это было:
def method(opts)
SomeOtherObject.some_other_method(opts)
end
Элегантный, простой, почти обман.
Есть ли ярлык для этих аргументов ключевого слова или мне нужно восстановить хэш-код параметров в вызове метода?
Ответы
Ответ 1
Да, это возможно, но это не очень элегантно.
Вам нужно будет использовать метод parameters
, который возвращает массив параметров метода и их типов (в этом случае мы имеем только аргументы ключевого слова).
def foo(one: 1, two: 2, three: 3)
method(__method__).parameters
end
#=> [[:key, :one], [:key, :two], [:key, :three]]
Зная, что существуют различные способы использования этого массива для получения хэша всех параметров и их предоставленных значений.
def foo(one: 1, two: 2, three: 3)
params = method(__method__).parameters.map(&:last)
opts = params.map { |p| [p, eval(p.to_s)] }.to_h
end
#=> {:one=>1, :two=>2, :three=>3}
Итак, ваш пример будет выглядеть как
def method(name: nil, color: nil, shoe_size: nil)
opts = method(__method__).parameters.map(&:last).map { |p| [p, eval(p.to_s)] }.to_h
SomeOtherObject.some_other_method(opts)
end
Подумайте об этом. Это умно, но за счет удобочитаемости, другим, читающим ваш код, это не понравится.
Вы можете сделать его более читаемым с помощью вспомогательного метода.
def params # Returns the parameters of the caller method.
caller_method = caller_locations(length=1).first.label
method(caller_method).parameters
end
def method(name: nil, color: nil, shoe_size: nil)
opts = params.map { |p| [p, eval(p.to_s)] }.to_h
SomeOtherObject.some_other_method(opts)
end
Обновление: В Ruby 2.2 представлен Binding#local_variables
, который можно использовать вместо Method#parameters
. Будьте осторожны, потому что вы должны вызвать local_variables
, прежде чем определять какие-либо дополнительные локальные переменные внутри метода.
# Using Method#parameters
def foo(one: 1, two: 2, three: 3)
params = method(__method__).parameters.map(&:last)
opts = params.map { |p| [p, eval(p.to_s)] }.to_h
end
#=> {:one=>1, :two=>2, :three=>3}
# Using Binding#local_variables (Ruby 2.2+)
def bar(one: 1, two: 2, three: 3)
binding.local_variables.params.map { |p|
[p, binding.local_variable_get(p)]
}.to_h
end
#=> {:one=>1, :two=>2, :three=>3}
Ответ 2
Конечно! Просто используйте оператор double splat (**
).
def print_all(**keyword_arguments)
puts keyword_arguments
end
def mixed_signature(some: 'option', **rest)
puts some
puts rest
end
print_all example: 'double splat (**)', arbitrary: 'keyword arguments'
# {:example=>"double splat (**)", :arbitrary=>"keyword arguments"}
mixed_signature another: 'option'
# option
# {:another=>"option"}
Он работает так же, как обычный символ (*
), используемый для сбора параметров. Вы даже можете переслать аргументы ключевого слова другому методу.
def forward_all(*arguments, **keyword_arguments, &block)
SomeOtherObject.some_other_method *arguments,
**keyword_arguments,
&block
end
Ответ 3
Мне было весело с этим, поэтому спасибо за это. Вот что я придумал:
describe "Argument Extraction Experiment" do
let(:experiment_class) do
Class.new do
def method_with_mixed_args(one, two = 2, three:, four: 4)
extract_args(binding)
end
def method_with_named_args(one:, two: 2, three: 3)
extract_named_args(binding)
end
def method_with_unnamed_args(one, two = 2, three = 3)
extract_unnamed_args(binding)
end
private
def extract_args(env, depth = 1)
caller_param_names = method(caller_locations(depth).first.label).parameters
caller_param_names.map do |(arg_type,arg_name)|
{ name: arg_name, value: eval(arg_name.to_s, env), type: arg_type }
end
end
def extract_named_args(env)
extract_args(env, 2).select {|arg| [:key, :keyreq].include?(arg[:type]) }
end
def extract_unnamed_args(env)
extract_args(env, 2).select {|arg| [:opt, :req].include?(arg[:type]) }
end
end
end
describe "#method_with_mixed_args" do
subject { experiment_class.new.method_with_mixed_args("uno", three: 3) }
it "should return a list of the args with values and types" do
expect(subject).to eq([
{ name: :one, value: "uno", type: :req },
{ name: :two, value: 2, type: :opt },
{ name: :three, value: 3, type: :keyreq },
{ name: :four, value: 4, type: :key }
])
end
end
describe "#method_with_named_args" do
subject { experiment_class.new.method_with_named_args(one: "one", two: 4) }
it "should return a list of the args with values and types" do
expect(subject).to eq([
{ name: :one, value: "one", type: :keyreq },
{ name: :two, value: 4, type: :key },
{ name: :three, value: 3, type: :key }
])
end
end
describe "#method_with_unnamed_args" do
subject { experiment_class.new.method_with_unnamed_args(2, 4, 6) }
it "should return a list of the args with values and types" do
expect(subject).to eq([
{ name: :one, value: 2, type: :req },
{ name: :two, value: 4, type: :opt },
{ name: :three, value: 6, type: :opt }
])
end
end
end
Я решил вернуть массив, но вы можете легко изменить это, чтобы вместо этого использовать хеш (например, не заботясь о типе аргумента после первоначального обнаружения).
Ответ 4
Как насчет синтаксиса ниже?
Для этого обработайте params
как зарезервированное ключевое слово в своем методе и поместите эту строку вверху метода.
def method(:name => nil, :color => nil, shoe_size => nil)
params = params(binding)
# params now contains the hash you're looking for
end
class Object
def params(parent_binding)
params = parent_binding.local_variables.reject { |s| s.to_s.start_with?('_') || s == :params }.map(&:to_sym)
return params.map { |p| [ p, parent_binding.local_variable_get(p) ] }.to_h
end
end