Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Rails 숨겨진 보석:ActiveSupport StringInquirer

Rails는 대규모 프레임워크이며 매년 규모가 커지고 있습니다. 이렇게 하면 일부 유용한 기능이 눈에 띄지 않게 지나치기 쉽습니다. 이 시리즈에서는 특정 작업을 위해 Rails에 내장된 덜 알려진 기능을 살펴보겠습니다.

이 시리즈의 첫 번째 기사에서는 Rails.env.test?를 호출하는 방법을 살펴보겠습니다. 실제로 잘 알려지지 않은 StringInquirer를 사용하여 내부적으로 작동합니다. ActiveSupport의 클래스입니다. 한 단계 더 나아가 StringInquirer 작동 방식을 확인하기 위한 소스 코드(스포일러 경고)는 특별한 method_missing을 통한 Ruby의 메타프로그래밍의 간단한 예입니다. 방법.

Rails.env 도우미

Rails 환경을 확인하는 코드를 이미 보았을 것입니다.

if Rails.env.test?
  # return hard-coded value...
else
  # contact external API...
end

그러나 test? 실제로 수행하며 이 방법은 어디에서 왔습니까?

Rails.env를 확인하면 콘솔에서는 문자열처럼 작동합니다.

=> Rails.env
"development"

여기서 문자열은 RAILS*ENV 환경 변수가 설정되는 모든 것입니다. 그러나 그것은 _단지* 문자열이 아닙니다. 콘솔에서 클래스를 읽을 때 다음이 표시됩니다.

=> Rails.env.class
ActiveSupport::StringInquirer

Rails.env 실제로 StringInquirer입니다.

StringInquirer

StringInquirer의 코드는 간단하고 잘 문서화되어 있습니다. 하지만 Ruby의 메타프로그래밍 기능에 익숙하지 않으면 작동 방식이 명확하지 않을 수 있습니다. 여기서 무슨 일이 일어나고 있는지 살펴보겠습니다.

class StringInquirer < String
...
end

먼저 StringInquirer가 String의 하위 클래스라는 것을 알 수 있습니다. 이것이 Rails.env가 호출할 때 String처럼 작동합니다. String에서 상속함으로써 모든 String과 유사한 기능을 자동으로 가져오므로 이를 String처럼 취급할 수 있습니다. Rails.env.upcase ActiveRecordModel.find_by(string_column: Rails.env)와 마찬가지로 작동합니다. .

Rails.env 동안 는 StringInquirer의 편리한 내장 예제이며, 직접 만들 수도 있습니다.

def type
  result = "old"
  result = "new" if @new

  ActiveSupport::StringInquirer.new(result)
end

그런 다음 반환된 값에 대한 물음표 메서드를 얻습니다.

=> @new = false
=> type.old?
true
=> type.new?
false
=> type.testvalue?
false

=> @new = true
=> type.new?
true
=> type.old?
false

방법_누락

method_missing 여기 진짜 비밀 소스입니다. Ruby에서 객체에 대한 메서드를 호출할 때마다 Ruby는 해당 메서드에 대한 객체의 모든 조상(즉, 기본 클래스 또는 포함된 모듈)을 살펴보는 것으로 시작합니다. 찾을 수 없으면 Ruby는 method_missing을 호출합니다. , 인수와 함께 찾고 있는 메서드의 이름을 전달합니다.

기본적으로 이 메서드는 예외를 발생시킵니다. 우리는 모두 NoMethodError: undefined method 'test' for nil:NilClass에 익숙할 것입니다. 오류 메시지 유형. 자체 method_missing을 구현할 수 있습니다. 예외를 발생시키지 않는 것은 정확히 StringInquirer 하는 일:

def method_missing(method_name, *arguments)
  if method_name.end_with?("?")
    self == method_name[0..-2]
  else
    super
  end
end

모든 ?로 끝나는 메소드 이름 , self의 값을 확인합니다. (즉, 문자열) ?가 없는 메서드 이름에 대해 . 달리 말하면 StringInquirer.new("test").long_test_method_name?을 호출하면 , 반환된 값은 "test" == "long_test_method_name"입니다. .

메소드 이름이 그렇지 않다면 물음표로 끝나면 원래 method_missing으로 돌아갑니다. (예외를 발생시키는 것).

respond_to_missing?

파일에 메소드가 하나 더 있습니다. respond_to_missing? . 이것이 method_missing의 동반 메소드라고 말할 수 있습니다. . method_missing 기능을 제공하지만 Ruby에 이러한 물음표 종료 메서드를 허용한다고 알리는 방법도 필요합니다.

def respond_to_missing?(method_name, include_private = false)
  method_name.end_with?("?") || super
end

이것은 respond_to?를 호출하면 작동합니다. 이 개체에. 이것이 없으면 StringInquirer.new("test").respond_to?(:test?)를 호출하면 , 결과는 false가 됩니다. test?라는 명시적 메서드가 없기 때문에 . Rails.env.respond_to?(:test?)를 호출하면 true를 반환할 것으로 예상하기 때문에 이것은 분명히 오해의 소지가 있습니다. .

respond_to_missing? 이것은 우리가 Ruby에게 "예, 그 방법을 처리할 수 있습니다"라고 말할 수 있게 해주는 것입니다. 메서드 이름이 물음표로 끝나지 않으면 슈퍼클래스 메서드로 대체합니다.

실용적인 사용 사례

이제 방법을 알게 되었으므로 StringInquirer가 작동합니다. 유용할 수 있는 인스턴스를 살펴보겠습니다.

1. 환경 변수

환경 변수는 StringInquirer에 대한 훌륭한 선택이 되는 두 가지 기준을 충족합니다. 첫째, 그들은 종종 제한적이고 알려진 가능한 값 세트(예:enum ), 그리고 두 번째로, 우리는 종종 그들의 값에 의존하는 조건부 논리를 가지고 있습니다.

앱이 결제 API에 연결되어 있고 환경 변수에 자격 증명이 저장되어 있다고 가정해 보겠습니다. 프로덕션 시스템에서는 이것이 분명히 실제 API이지만 스테이징 또는 개발 버전에서는 샌드박스 API를 사용하려고 합니다.

# ENV["PAYMENT_API_MODE"] = sandbox/production

class PaymentGateway
  def api_mode
    # We use ENV.fetch because we want to raise if the value is missing
    @api_mode ||= ENV.fetch("PAYMENT_API_MODE").inquiry
  end

  def api_url
    # Pro-tip: We *only* use production if MODE == 'production', and default
    # to sandbox if the value is anything else, this prevents us using production
    # values if the value is mistyped or incorrect
    if api_mode.production?
      PRODUCTION_URL
    else
      SANDBOX_URL
    end
  end
end

위의 내용에서는 ActiveSupport의 String#inquiry를 사용하고 있습니다. String을 StringInquirer로 쉽게 변환하는 메서드입니다.

2. API 응답

위의 결제 API 예제를 계속하면 API는 일부 성공/실패 상태가 포함된 응답을 보냅니다. 다시 말하지만, 이는 StringInquirer의 후보가 되는 두 가지 기준, 즉 제한된 가능한 값 집합과 이러한 값을 테스트할 조건부 논리를 충족합니다.

class PaymentGateway
  def create_charge
    response = JSON.parse(api_call(...))

    result = response["result"].inquiry

    if result.success?
      ...
    else
      ...
    end

    # result still acts like a string
    Rails.logger.info("Payment result was: #{result}")
  end
end

결론

StringInquirer는 뒷주머니에 넣고 다닐 수 있는 흥미로운 도구이지만 개인적으로 너무 자주 사용하지는 않습니다. 몇 가지 용도가 있지만 대부분의 경우 객체에 대한 명시적 메서드가 동일한 결과를 제공합니다. 명시적으로 명명된 메서드에도 몇 가지 이점이 있습니다. 값을 변경해야 하는 경우 한 장소만 업데이트하면 됩니다. 개발자가 메서드를 찾으려고 할 때 코드베이스를 더 쉽게 검색할 수 있습니다.

StringInquirer에 중점을 두고 있지만 이 기사는 method_missing을 사용하는 Ruby의 일부 메타프로그래밍 기능에 대해 좀 더 부드럽게 소개하기 위한 것입니다. . 아마도 method_missing을 사용하고 싶지 않을 것입니다. 당신의 응용 프로그램에서. 그러나 Rails 또는 gem에서 제공하는 도메인별 언어와 같은 프레임워크에서 자주 사용되므로 문제가 발생할 때 "소시지가 어떻게 만들어지는지"를 아는 것이 매우 도움이 될 수 있습니다.