Ruby에서 메서드 인수에 액세스하는 방법이 있습니까?


102

Ruby와 ROR을 처음 접하고 매일 그것을 좋아하므로 Google을 어떻게하는지 몰랐기 때문에 여기에 내 질문이 있습니다 (그리고 시도했습니다 :))

우리는 방법이 있습니다

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이기 때문에 나는 방법이 있다고 가정합니다 :) 그것이 자바라면 나는 그들을 나열 할 것입니다 :)

출력은 다음과 같습니다.

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

1
Reha kralj svegami!
개미

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 이것은 정말 멋지고 우아합니다. 확실히 최고의 대답입니다.
Andrew Marshall

5
Ruby 1.9.3을 사용해 보았지만 작동하려면 # {eval arg.to_s}를 수행해야합니다. 그렇지 않으면 TypeError : ca n't convert Symbol into String
Javid Jamae

5
한편, 내 기술과 능력이 향상되었고 이제이 코드를 이해했습니다.
Haris Krajina

55

Ruby 2.1부터 binding.local_variable_get 을 사용 하여 메서드 매개 변수 (인수)를 포함한 모든 지역 변수의 값을 읽을 수 있습니다 . 덕분에 허용되는 답변 을 개선하여 eval.

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

감사. 이렇게하면 좋습니다 : logger.info method __ + "# {args.inspect}"method ( _method ) .parameters.map do | , 이름 | logger.info "# {name} ="+ binding.local_variable_get (name) end
Martin Cleaver

이것이 갈 길이다.
Ardee 아람

1
또한 잠재적으로 유용합니다-인수를 명명 된 해시로 변환 :Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

이를 처리하는 한 가지 방법은 다음과 같습니다.

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

이건 그렇게 나쁘지 않은데 왜이 악한 해결책인가?
Haris Krajina 2012

이 경우 나쁘지 않습니다. eval ()을 사용하면 보안 허점이 발생할 수 있습니다. 그냥 난 더 나은 방법이있을 수있다 생각 :)하지만 구글이이 경우에 우리의 친구없는 인정
라파엘

나는 이것으로 갈거야, 단점은 당신이 이것을 처리 할 도우미 (모듈)를 만들 수 없다는 것입니다. 왜냐하면 그것이 원래의 메소드를 떠나 자마자 로컬 변수의 평가를 할 수 없기 때문입니다. 정보를 제공해 주셔서 감사합니다.
Haris Krajina 2012

이렇게하면 "TypeError : 기호를 문자열로 변환 할 수 없습니다"라는 메시지가 표시됩니다 eval var.to_s. 또한주의 할 점은 이 루프 실행 하기 전에 지역 변수를 정의 하면 메서드 매개 변수에 추가로 포함된다는 것입니다.
Andrew Marshall

6
이것은 가장 우아하고 안전한 접근 방식이 아닙니다. 메서드 내에서 지역 변수를 정의한 다음를 호출 local_variables하면 메서드 인수와 모든 지역 변수가 반환됩니다. 이로 인해 코드가 잘못 될 수 있습니다.
Aliaksei Kliuchnikau 2012

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 2012

나보다 똑똑한 사람을 인용하기 위해 Bob Martin은 그의 저서 Clean Code에서 "함수에 대한 이상적인 인수 수는 0 (niladic)입니다. 다음은 1 (모노 아딕), 그 다음에는 2 (이원))입니다. (삼각형)은 가능하면 피해야합니다. 세 개 이상 (다각형)은 매우 특별한 타당성을 요구하며 어쨌든 사용해서는 안됩니다. " 그는 내가 말한 것을 계속해서 말합니다. 많은 관련 인수는 클래스로 래핑되고 객체로 전달되어야합니다. 좋은 책인데 적극 추천합니다.
Tom L

그것에 대해 너무 세밀하지 말고 이것을 고려하십시오. 더 많거나 적거나 다른 인수가 필요하다는 것을 발견하면 API가 손상되고 해당 메서드에 대한 모든 호출을 업데이트해야합니다. 반면에 객체를 전달하면 앱의 다른 부분 (또는 서비스 소비자)이 즐겁게 움직일 수 있습니다.
Tom L

나는 귀하의 요점에 동의하며 예를 들어 Java에서는 항상 귀하의 접근 방식을 시행합니다. 하지만 ROR 다른이며, 여기에 생각하는 이유 :
하리스 크라

나는 귀하의 요점에 동의하며 예를 들어 Java에서는 항상 귀하의 접근 방식을 시행합니다. 하지만 ROR과는 다르다고 생각합니다. 그 이유는 다음과 같습니다. ActiveRecord를 DB에 저장하고 저장하는 방법이 있다면 저장 방법에 전달하기 전에 해시를 어셈블해야합니다. 사용자 예를 들어 이름, 성, 사용자 이름 등을 설정하고 해시를 전달하여 작업을 수행하고 저장하는 방법을 저장합니다. 그리고 여기에 모든 개발자가 해시에 넣을 것을 어떻게 알 수 있습니까? 활성 레코드이므로 해시를 어셈블하는 것보다 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_errorsgem 을 추천 합니다.
Haris Krajina

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.