Rubyのメソッド引数にアクセスする方法はありますか?


102

RubyとRORの新機能であり、毎日それを愛しているので、これをググる方法がわからない(そして私は:)を試したので、これが私の質問です。

私たちは方法を持っています

def foo(first_name, last_name, age, sex, is_plumber)
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"    
end

だから私はすべての引数をリストすることなく、メソッドに渡す方法を探しています。これはRubyなので、方法があると思います:)もしそれがjavaだったら、それらをリストするだけです:)

出力は次のようになります:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}

1
Reha kralj svegami!
2016

1
引数の発見メソッドが実行される前に「コード」が引数の値を変更すると、渡された値ではなく新しい値が表示されることをすべての答えが指摘していると思います。それらを正しく取得する必要があります確かに離れて。:それは(前の回答に与えられたクレジット付き)、このための私のお気に入りのワンライナーである、と述べたmethod(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
ブライアンDeterling

回答:


159

Ruby 1.9.2以降では、parametersメソッドでメソッドを使用して、そのメソッドのパラメーターのリストを取得できます。これは、パラメーターの名前とそれが必要かどうかを示すペアのリストを返します。

例えば

もし、するなら

def foo(x, y)
end

その後

method(:foo).parameters # => [[:req, :x], [:req, :y]]

特殊変数__method__を使用して、現在のメソッドの名前を取得できます。したがって、メソッド内では、そのパラメータの名前は

args = method(__method__).parameters.map { |arg| arg[1].to_s }

次に、各パラメータの名前と値を表示することができます

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')

注:この回答はもともと書かれていたため、現在のバージョンのRuby evalではシンボルを使用して呼び出すことはできません。これに対処するためにto_s、パラメータ名のリストを作成するときに明示が追加されました。parameters.map { |arg| arg[1].to_s }


4
これを解読するには時間が必要です:)
Haris Krajina

3
解読が必要なビットを教えてください。説明を追加します:)
mikej

3
+1これは本当に素晴らしくてエレガントです。間違いなく最良の答えです。
アンドリューマーシャル

5
Ruby 1.9.3を試しましたが、機能させるには#{eval arg.to_s}を実行する必要があります。そうしないと、TypeErrorが発生し、シンボルを文字列に変換できません
Javid Jamae

5
その間に、私のスキルが良くなり、このコードを理解するようになりました。
Haris Krajina

55

Ruby 2.1以降、binding.local_variable_getを使用して、メソッドパラメータ(引数)を含む任意のローカル変数の値を読み取ることができます。そのおかげで、あなたは受け入れられた答えを改善して回避することができます悪の 評価。

def foo(x, y)
  method(__method__).parameters.map do |_, name|
    binding.local_variable_get(name)
  end
end

foo(1, 2)  # => 1, 2

2.1は最も古いですか?
uchuugaka

@uchuugakaええ、この方法は<2.1では利用できません。
Jakub Jirutka、2015

ありがとう。これはいいことです:logger.info method __ + "#{args.inspect}" method(_method).parameters.map do | 、名前| logger.info "#{name} =" + binding.local_variable_get(name)end
Martin Cleaver

これが方法です。
Ardee Aram

1
また、潜在的に有用-引数を名前付きハッシュに変換します:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba '27

19

これを処理する1つの方法は次のとおりです。

def foo(*args)
    first_name, last_name, age, sex, is_plumber = *args
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{args.inspect}"    
end

2
動作しており、より良い答えがない限り、承認されたとして投票されます。これに関する私の唯一の問題は、メソッドの署名を失いたくないことです。Inteliの感覚があり、失いたくない場合もあります。
Haris Krajina、2012

7

これは興味深い質問です。多分local_variablesを使用してますか?ただし、evalを使用する以外の方法が必要です。カーネルのドキュメントを探しています

class Test
  def method(first, last)
    local_variables.each do |var|
      puts eval var.to_s
    end
  end
end

Test.new().method("aaa", 1) # outputs "aaa", 1

これはそれほど悪くはありません、なぜこの邪悪な解決策なのですか?
ハリスクラジナ

この場合は問題ありません-eval()を使用すると、セキュリティホールが発生する場合があります。より良い方法があると思うだけです:)しかし、私はこの場合Googleが私たちの友人ではないことを認めます
ラファエレ

私はこれを使用しますが、欠点は、元のメソッドを離れるとすぐにローカル変数の評価を行うことができないため、これを処理するヘルパー(モジュール)を作成できないことです。情報をありがとう。
Haris Krajina

これにより、に変更しない限り、「TypeError:シンボルを文字列に変換できません」というメッセージが表示されeval var.to_sます。また、このループの実行前にローカル変数を定義すると、メソッドパラメータに加えてローカル変数も含まれることに注意してください。
アンドリューマーシャル

6
これは最もエレガントで安全な方法ではありません。メソッド内でローカル変数を定義してを呼び出すとlocal_variables、メソッドの引数とすべてのローカル変数が返されます。これにより、コードの実行時にエラーが発生する可能性があります。
Aliaksei Kliuchnikau

5

これは役に立つかもしれません...

  def foo(x, y)
    args(binding)
  end

  def args(callers_binding)
    callers_name = caller[0][/`.*'/][1..-2]
    parameters = method(callers_name).parameters
    parameters.map { |_, arg_name|
      callers_binding.local_variable_get(arg_name)
    }    
  end

1
むしろ、この少しハックよりcallers_name実装、あなたも渡すことができる__method__と一緒にbinding
Tom Lord、

3

次のような定数を定義できます。

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"

そしてあなたのコードでそれを使ってください:

args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)

2

先に進む前に、fooに渡す引数が多すぎます。これらの引数はすべてモデルの属性のようです、そうですか?実際にはオブジェクト自体を渡す必要があります。スピーチの終わり。

「splat」引数を使用できます。すべてを配列に押し込みます。それは次のようになります:

def foo(*bar)
  ...
  log.error "Error with arguments #{bar.joins(', ')}"
end

これに同意しない場合、メソッドシグネチャはコードの可読性と再利用性にとって重要です。オブジェクト自体は問題ありませんが、インスタンスをどこかに作成する必要があるため、メソッドを呼び出す前、またはメソッド内で呼び出します。私の意見では方法の方が優れています。たとえば、ユーザーメソッドを作成します。
Haris Krajina

私よりも賢い人、ボブマーティンは、彼の著書であるClean Codeで「関数の引数の理想的な数はゼロ(ニラディック)です。 (3項)は可能な限り回避する必要があります。3項(3項)を超えると非常に特別な正当化が必要になるため、使用しないでください。」彼は私が言ったことを続けます、多くの関連する引数はクラスにラップされ、オブジェクトとして渡されるべきです。それは良い本です、私はそれを強くお勧めします。
トムL

細かい点を付けすぎないでください。ただし、これを検討してください。引数が多い/少ない/異なる必要がある場合は、APIが壊れているため、そのメソッドへのすべての呼び出しを更新する必要があります。一方、オブジェクトを渡すと、アプリの他の部分(またはサービスのコンシューマー)が楽に移動できます。
トムL

私はあなたの主張に同意します。たとえば、Javaでは常にあなたのアプローチを強制します。しかし、私はRORが異なっており、ここにあると考える理由:
ハリスクライナ

私はあなたの主張に同意します。たとえば、Javaでは常にあなたのアプローチを強制します。しかし、RORとは異なると思いますが、その理由は次のとおりです。ActiveRecordをDBに保存し、それを保存するメソッドがある場合、saveメソッドに渡す前にハッシュをアセンブルする必要があります。ユーザーの例では、姓、名、ユーザー名などを設定し、ハッシュを渡して、何かを実行して保存するsaveメソッドに渡します。そして、ここに問題があります。どの開発者がハッシュに何を入れるかをどのように知るのですか?これはアクティブなレコードなので、ハッシュをアセンブルするよりもdbスキーマを確認する必要があり、記号を見落とさないように十分注意してください。
Haris Krajina

2

メソッドシグネチャを変更する場合は、次のようなことができます。

def foo(*args)
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{args}"    
end

または:

def foo(opts={})
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{opts.values}"    
end

この場合、補間argsまたはopts.values配列になりますが、あなたのことができjoinカンマであれば。乾杯


2

この質問が達成しようとしていることは、私がリリースしたばかりの宝石、https://github.com/ericbeland/exception_detailsで行うことができるようです。救出された例外からローカル変数と値(およびインスタンス変数)をリストします。一見の価値があるかもしれません...


1
これは素晴らしい宝石です。Railsユーザーには、私もbetter_errors宝石をお勧めします。
Haris Krajina 2013

1

ハッシュとして引数が必要で、パラメーターのトリッキーな抽出でメソッドの本体を汚染したくない場合は、次のようにします。

def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
  args = MethodArguments.(binding) # All arguments are in `args` hash now
  ...
end

このクラスをプロジェクトに追加するだけです:

class MethodArguments
  def self.call(ext_binding)
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
    method_name = ext_binding.eval("__method__")
    ext_binding.receiver.method(method_name).parameters.map do |_, name|
      [name, ext_binding.local_variable_get(name)]
    end.to_h
  end
end

0

関数がクラス内にある場合、次のようなことができます:

class Car
  def drive(speed)
  end
end

car = Car.new
method = car.method(:drive)

p method.parameters #=> [[:req, :speed]] 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.