Подтвердить что ты не робот

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

Элегантный, простой, почти обман.

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

4b9b3361

Ответ 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