2014-02-25 16 views
25

Ich habe eine Methode, die so aussieht:Ruby - Keyword-Argumente - Können Sie alle Keyword-Argumente als Hash behandeln? Wie?

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

Für jeden Anruf gegeben, ich eine beliebige Kombination von optionalen Werten annehmen kann. Ich mag die genannten Argumente, weil ich mir einfach die Signatur der Methode ansehen kann, um zu sehen, welche Optionen verfügbar sind.

Was ich nicht weiß ist, ob es eine Abkürzung für das gibt, was ich im Codebeispiel oben in Großbuchstaben beschrieben habe.

Zurück in den alten Tagen, verwendet es zu sein:

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

Elegant, schlicht Betrug, fast.

Gibt es eine Verknüpfung für diese Schlüsselwort Argumente oder muss ich meine Optionen Hash in der Methodenaufruf wiederherstellen?

Antwort

7

Natürlich! Verwenden Sie einfach den Doppel-Splat-Operator (**).

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

Es funktioniert genau wie die regelmäßige Splat (*), für das Sammeln von Parametern. Sie können die Schlüsselwortargumente sogar an eine andere Methode weiterleiten.

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

Nein, ich bin an alle optionalen, mit dem Namen Keyword-Parameter in eine Hash-Sammlung. Ich versuche nicht, einen neuen Options-Hash zu erstellen. Ich möchte einen Hash von {: name => val,: color => val, etc.}, die in der Methodensignatur benannt sind. –

+1

Ich auch. Ich bin überrascht, dass dies nicht eingebaut ist .... – danielricecodes

15

Ja, das ist möglich, aber es ist nicht sehr elegant.

Sie müssen verwenden, die ein Array der Parameter der Methode und ihrer Typen zurückgibt (in diesem Fall haben wir nur Schlüsselwortargumente).

Wissen, dass es verschiedene Möglichkeiten gibt, dieses Array zu verwenden, um einen Hash aller Parameter und ihrer bereitgestellten Werte zu erhalten.

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} 

So Ihr Beispiel würde so aussehen

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 

sorgfältig Denken Sie darüber verwenden. Es ist schlau, aber auf Kosten der Lesbarkeit werden andere Ihren Code nicht lesen.

Sie können es mit einer Hilfsmethode leicht lesbarer machen.

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 

Update: Ruby-2.2 Binding#local_variables eingeführt, die anstelle von Method#parameters verwendet werden kann. Seien Sie vorsichtig, da Sie local_variables aufrufen müssen, bevor Sie zusätzliche lokale Variablen innerhalb der Methode definieren.

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

Ist das Problem, wir wollen es nicht wie folgt neu zu sagen? def foo (ein: 1, zwei: 2, drei: 3) params = {eins: eins, zwei: zwei, drei: drei)} Ende Was ist der Vorteil davon aus, wie Ihre Beispiele zu schreiben? Es scheint verwirrend. Auch eine andere Frage, wie legitim diese Implementierung ist? Ist das eine gute Möglichkeit, named_params zu verwenden? – jmoon90

+1

@ jmoon90 das ist richtig, wir wollen nicht 'params = {...}' machen, weil wir dann die Implementierung hartkodieren und es wird sehr gekoppelt. Der Vorteil, wie in meinen Beispielen, besteht darin, dass Sie die Methodensignatur ändern und dennoch alle Schlüsselwortparameter automatisch erfassen können. Ich bin mir nicht sicher, ob ich deine andere Frage zu 'named_params' verstehe. – Dennis

+1

Besser 'binding.local_variable_get (p)' anstelle von 'eval (p.to_s)' zu verwenden, wenn 'local_variables' verwendet wird, um das böse' eval' zu vermeiden ... – Markus

1

Ich hatte etwas Spaß damit, also danke dafür. Hier ist, was ich kam mit:

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 

Ich entschied mich für ein Array zurück, aber man konnte leicht modifizieren diese stattdessen einen Hash zurück (zum Beispiel, indem sie nicht die Sorge um das Argument Typ nach der ersten Detektion).

0

Wie wäre es mit der Syntax unten?

Damit es funktioniert, behandeln Sie params als reserviertes Schlüsselwort in Ihrer Methode und platzieren Sie diese Zeile an der Spitze der Methode.

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 
+0

Warum "verwerfen" Sie Variablen, die mit 'beginnen _'? –

+1

Ein Argumentname mit einem führenden Unterstrich ist die empfohlene Methode, um ein Argument anzugeben, das in der Methode oder im Block gemäß dem Ruby Style Guide nicht verwendet wird. Tatsächlich behandelt Ruby Variablen namens _ anders als andere Variablen. Weitere Informationen finden Sie unter http://po-ru.com/diary/rubys-magic-underscore/. Diese Linie in Abdos Lösung filtert sie einfach heraus, so dass sie nicht weitergegeben werden. –