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

ค้นพบอัญมณีแฝงของ Rails: เชี่ยวชาญ ActiveSupport StringInquirer

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

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

The Rails.env helper (ตัวช่วย Rails.env)

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

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

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

หากเราตรวจสอบ Rails.env ในคอนโซล มันทำงานเหมือนสตริง:

=> Rails.env "development" 

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

=> Rails.env.class ActiveSupport::StringInquirer 

Rails.env จริงๆ แล้วคือ StringInquirer

StringInquirer

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

class StringInquirer < String ... end 

ขั้นแรก เราเห็นว่า StringInquirer เป็น subclass ของ String นี่คือเหตุผลที่ Rails.env ทำงานเหมือน String เมื่อเราเรียกมัน ด้วยการสืบทอดจาก String เราได้ฟังก์ชันลักษณะ String ทั้งหมดโดยอัตโนมัติ ดังนั้นเราจึงจัดการมันได้เหมือน String Rails.env.upcase ทำงาน ได้เหมือนกัน ActiveRecordModel.find_by(string_column: Rails.env)

แม้ Rails.env จะเป็นตัวอย่างที่สะดวกและ built-in ของ 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 เริ่มต้นด้วยการดูที่อานเซสเตอร์ทั้งหมดของออบเจ็กต์ (คือ คลาสฐานหรือโมดูลที่ include) สำหรับเมธอด หากไม่พบ Ruby จะเรียก method_missing โดยส่งชื่อของเมธอดที่มันกำลังค้นหา พร้อมกับอาร์กิวเมนต์ต่างๆ

โดยค่าเริ่มต้น เมธอดนี้แค่ raise exception เราทุกคนน่าจะคุ้นเคยกับข้อความข้อผิดพลาด NoMethodError: undefined method 'test' for nil:NilClass ประเภทนี้ เราสามารถ implement method_missing ของตัวเองซึ่งไม่ raise exception ซึ่งเป็นสิ่งที่ StringInquirer ทำอย่างแม่นยำ:

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

สำหรับ ทุก ชื่อเมธอดที่ลงท้ายด้วย ? เราตรวจสอบค่าของ self (คือ String) ต่อชื่อเมธอดโดยไม่มี ? กล่าวอีกนัยหนึ่ง หากเราเรียก StringInquirer.new("test").long_test_method_name? ค่าที่คืนคือ "test" == "long_test_method_name"

หากชื่อเมธอด ไม่ ลงท้ายด้วยเครื่องหมายคำถาม เราจะ fallback ไปที่ method_missing ต้นฉบับ (ตัวที่จะ raise exception)

respond_to_missing?

มีอีกหนึ่งเมธอดในไฟล์: respond_to_missing? เราอาจบอกว่านี่คือเมธอด pendants ของ 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? นี่ชัดเจนว่าผิดทาง เพราะฉันคาดว่ามันจะคืนค่า true หากฉันเรียก Rails.env.respond_to?(:test?)

respond_to_missing? คือสิ่งที่อนุญาตให้เราบอก Ruby ว่า "ใช่ ฉันจัดการเมธอดนั้นได้" หากชื่อเมธอดไม่ลงท้ายด้วยเครื่องหมายคำถาม เรา fallback ไปที่เมธอดของซูเปอร์คลาส

กรณีการใช้งานจริง (Practical use cases)

ตอนนี้เรารู้ วิธี ที่ StringInquirer ทำงาน มาดูกรณีที่มันอาจมีประโยชน์:

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

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

สมมติว่าแอปของเราเชื่อมต่อกับ Payment API และเรามี credentials เก็บในตัวแปรสภาพแวดล้อม ในระบบ production นี่ชัดเจนว่าเป็น API จริง แต่ใน staging หรือ development เราต้องการใช้ sandbox API ของพวกเขา:

# ENV["PAYMENT_API_MODE"] = sandbox/production class PaymentGateway def api_mode # เราใช้ ENV.fetch เพราะเราอยากให้ raise หากค่าหายไป @api_mode ||= ENV.fetch("PAYMENT_API_MODE").inquiry end def api_url # Pro-tip: เรา *เพียง* ใช้ production ถ้า MODE == 'production' และ default # ไป sandbox หากค่าเป็นอะไรอื่นๆ นี่ป้องกันไม่ให้เราใช้ค่า production # หากค่าถูกพิมพ์ผิดหรือไม่ถูกต้อง if api_mode.production? PRODUCTION_URL else SANDBOX_URL end end end 

สังเกตว่าในข้างบน เราใช้เมธอด String#inquiry ของ ActiveSupport ซึ่งแปลง String ให้เป็น StringInquirer ให้เราอย่างสะดวก

2. การตอบสนองของ API (API responses)

ต่อจากตัวอย่าง Payment API ข้างบน API จะส่ง response กลับมาที่รวมสถานะ success/failure ไปด้วย อีกครั้ง นี่ตรงตามเกณฑ์สองประการที่ทำให้เป็นผู้สมัคร StringInquirer: ชุดค่าที่เป็นไปได้จำกัด และ logic แบบมีเงื่อนไขที่จะทดสอบค่าเหล่านี้

class PaymentGateway def create_charge response = JSON.parse(api_call(...)) result = response["result"].inquiry if result.success? ... else ... end # result ยังคงทำงานเหมือนสตริง Rails.logger.info("Payment result was: #{result}") end end 

สรุป (Conclusion)

StringInquirer เป็นเครื่องมือที่น่าสนใจที่จะมีในกระเป๋า แต่ส่วนตัวผมไม่ค่อยจะเอามาใช้บ่อย มันมีการใช้งานบางอย่าง แต่ส่วนใหญ่เมธอดชัดเจนบนออบเจ็กต์ให้ผลลัพธ์เดียวกัน เมธอดที่ตั้งชื่ออย่างชัดเจนยังมีข้อได้เปรียบสองประการ หากค่าต้องเปลี่ยน คุณต้องอัปเดตที่เดียว *และ* มันทำให้โค้ดเบสค้นหาง่ายขึ้น หากนักพัฒนาพยายามค้นหาเมธอด

แม้จะโฟกัสที่ StringInquirer บทความนี้ตั้งใจเป็นการแนะนำอย่างอ่อนโยนสู่ความสามารถ metaprogramming บางอย่างของ Ruby โดยใช้ method_missing ผมจะบอกว่าคุณอาจไม่ควรใช้ method_missing ในแอปของคุณ อย่างไรก็ตาม มันมักใช้ในเฟรมเวิร์ก เช่น Rails หรือ domain-specific-languages ที่ gems จัดเตรียม ดังนั้นการรู้ "ว่าแฮมทำอย่างไร" (how the sausage is made) อาจช่วยได้มากเมื่อคุณพบปัญหา