Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Rails Hidden Gems:ActiveSupport StringInquirer

Rails เป็นเฟรมเวิร์กขนาดใหญ่ และเติบโตขึ้นทุกปี สิ่งนี้ทำให้คุณสมบัติที่เป็นประโยชน์บางอย่างง่ายขึ้นโดยไม่มีใครสังเกตเห็น ในชุดนี้ เราจะมาดูฟังก์ชันที่ไม่ค่อยมีใครรู้จักซึ่งรวมอยู่ใน Rails สำหรับงานเฉพาะ

ในบทความแรกในชุดนี้ เราจะมาดูวิธีการเรียก Rails.env.test? ใช้งานได้จริงภายใต้ประทุนโดยใช้ StringInquirer . ที่ไม่ค่อยมีใครรู้จัก คลาสจาก ActiveSupport ก้าวไปอีกขั้น เราจะพูดถึง StringInquirer ซอร์สโค้ดเพื่อดูว่ามันทำงานอย่างไร ซึ่ง (การแจ้งเตือนสปอยเลอร์) เป็นตัวอย่างง่ายๆ ของการเขียนโปรแกรมเมตาของ Ruby ผ่าน method_missing พิเศษ วิธีการ

ตัวช่วย Rails.env

คุณอาจเคยเห็นโค้ดที่ตรวจสอบสภาพแวดล้อม Rails แล้ว:

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

อย่างไรก็ตาม test? ทำได้จริง และวิธีนี้มาจากไหน

ถ้าเราตรวจสอบ Rails.env ในคอนโซลจะทำหน้าที่เหมือนสตริง:

=> Rails.env
"development"

สตริงที่นี่คือตัวแปรสภาพแวดล้อม RAILS*ENV ที่ตั้งค่าไว้ มันไม่ใช่ _just* สตริงแม้ว่า เมื่ออ่านชั้นเรียนในคอนโซล เราจะเห็น:

=> Rails.env.class
ActiveSupport::StringInquirer

Rails.env จริงๆ แล้วเป็น StringInquirer

StringInquirer

รหัสสำหรับ StringInquirer มีทั้งแบบสั้นและจัดทำเป็นเอกสารอย่างดี วิธีการทำงานอาจไม่ชัดเจน เว้นแต่คุณจะคุ้นเคยกับความสามารถในการเขียนโปรแกรมเมตาของ Ruby เราจะพูดถึงสิ่งที่เกิดขึ้นที่นี่

class StringInquirer < String
...
end

อันดับแรก เราจะเห็นว่า StringInquirer เป็นคลาสย่อยของ String นี่คือเหตุผลที่ Rails.env ทำหน้าที่เหมือนสตริงเมื่อเราเรียกมันว่า เมื่อรับค่าจาก 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

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? . เราอาจกล่าวได้ว่านี่เป็นวิธีการร่วมในการ 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?) .

respond_to_missing? คือสิ่งที่ทำให้เราพูดว่า "ใช่ ฉันจัดการวิธีนั้นได้" กับ Ruby หากชื่อเมธอดไม่ได้ลงท้ายด้วยเครื่องหมายคำถาม เราจะถอยกลับไปที่เมธอด superclass

กรณีใช้งานจริง

ตอนนี้เรารู้แล้วว่า อย่างไร StringInquirer ใช้งานได้ ลองดูตัวอย่างที่อาจมีประโยชน์:

1. ตัวแปรสภาพแวดล้อม

ตัวแปรสภาพแวดล้อมตรงตามเกณฑ์สองข้อที่ทำให้พวกเขาเป็นตัวเลือกที่ยอดเยี่ยมสำหรับ StringInquirer ประการแรก พวกเขามักจะมีชุดค่าที่เป็นไปได้ที่จำกัดและเป็นที่รู้จัก (เช่น enum ) และอย่างที่สอง เรามักจะมีตรรกะตามเงื่อนไขที่ขึ้นอยู่กับค่าของมัน

สมมติว่าแอปของเราเชื่อมต่อกับ API การชำระเงิน และเรามีข้อมูลประจำตัวที่จัดเก็บไว้ในตัวแปรสภาพแวดล้อม ในระบบการผลิตของเรา เห็นได้ชัดว่านี่เป็น API จริง แต่ในเวอร์ชันการจัดเตรียมหรือเวอร์ชันการพัฒนา เราต้องการใช้ Sandbox 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

โปรดทราบว่าในข้างต้น เรากำลังใช้ String#inquiry . ของ ActiveSupport เมธอดซึ่งแปลง 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 แต่บทความนี้มีวัตถุประสงค์เพื่อเป็นการแนะนำเบื้องต้นเกี่ยวกับความสามารถในการเขียนโปรแกรมเมตาของ Ruby โดยใช้ method_missing . ฉันจะบอกว่าคุณอาจไม่ต้องการใช้ method_missing ในใบสมัครของคุณ อย่างไรก็ตาม มักใช้ในเฟรมเวิร์ก เช่น Rails หรือภาษาเฉพาะโดเมนที่ให้บริการโดย gem ดังนั้นจึงเป็นประโยชน์มากที่จะรู้ว่า "วิธีทำไส้กรอก" เมื่อคุณประสบปัญหา