Techioz Blog

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

エレガント、シンプル、そしてほとんど騙されています。

これらのキーワード引数のショートカットはありますか、それともメソッド呼び出しでオプション ハッシュを再構成する必要がありますか?

解決策

はい、これは可能ですが、あまりエレガントではありません。

メソッドのパラメータとその型の配列を返すパラメータ メソッドを使用する必要があります (この場合はキーワード引数のみです)。

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 では、Method#parameters の代わりに使用できる Binding#local_variables が導入されました。メソッド内で追加のローカル変数を定義する前に、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}