ruby hash arguments keyword options

Ruby: argumentos de las palabras clave: ¿puedes tratar todos los argumentos de palabras clave como un hash? ¿Cómo?



arguments keyword (4)

¡Por supuesto! Solo usa el operador 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"}

Funciona igual que el splat normal ( * ), que se usa para recolectar parámetros. Incluso puede reenviar los argumentos de la palabra clave a otro método.

def forward_all(*arguments, **keyword_arguments, &block) SomeOtherObject.some_other_method *arguments, **keyword_arguments, &block end

Tengo un método que se ve así:

def method(:name => nil, :color => nil, shoe_size => nil) SomeOtherObject.some_other_method(THE HASH THAT THOSE KEYWORD ARGUMENTS WOULD MAKE) end

Para cualquier llamada dada, puedo aceptar cualquier combinación de valores opcionales. Me gustan los argumentos nombrados, porque puedo mirar la firma del método para ver qué opciones están disponibles.

Lo que no sé es si hay un atajo para lo que he descrito en letras mayúsculas en el ejemplo de código anterior.

En los viejos tiempos, solía ser:

def method(opts) SomeOtherObject.some_other_method(opts) end

Elegante, simple, casi infiel.

¿Hay un atajo para esos argumentos clave o tengo que reconstituir mi hash de opciones en la llamada al método?


¿Qué tal la sintaxis a continuación?

Para que funcione, trate los params como una palabra clave reservada en su método y coloque esta línea en la parte superior del método.

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


Me divertí un poco con esto, así que gracias por eso. Esto es lo que se me ocurrió:

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

Elegí devolver una matriz, pero podría modificar esto fácilmente para devolver un hash en su lugar (por ejemplo, al no preocuparse por el tipo de argumento después de la detección inicial).


Sí, esto es posible, pero no es muy elegante.

Deberá usar el método de parameters , que devuelve una matriz de los parámetros del método y sus tipos (en este caso, solo tenemos argumentos de palabra clave).

def foo(one: 1, two: 2, three: 3) method(__method__).parameters end #=> [[:key, :one], [:key, :two], [:key, :three]]

Sabiendo eso, hay varias formas de usar esa matriz para obtener un hash de todos los parámetros y sus valores proporcionados.

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}

Entonces tu ejemplo se vería

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

Piense con cuidado acerca de usar esto. Es inteligente pero a costa de la legibilidad, a otros que leen tu código no les gustará.

Puedes hacerlo un poco más legible con un método de ayuda.

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

Actualización: Ruby 2.2 introdujo Binding#local_variables que se puede usar en lugar de los Method#parameters del Method#parameters . Tenga cuidado porque debe llamar a local_variables antes de definir cualquier variable local adicional dentro del método.

# 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}