โมดูล (และคลาส) มีไว้เพื่อซ้อนกัน เศษโค้ด เช่น ActiveRecord::RecordNotFound
เป็นเรื่องธรรมดามากจนเราไม่คิดถึงเรื่องนี้ซ้ำสอง แต่สิ่งที่ฝังอยู่ภายในการติดตั้งแบบซ้อนของ Ruby - และระบบโหลดอัตโนมัติของ Rails - เป็นกับดักสองสามอย่างที่อาจทำให้โค้ดของคุณล้มเหลวในรูปแบบที่แปลกและมหัศจรรย์ ในโพสต์นี้ เราจะพูดถึงที่มาของกับดักเหล่านี้และวิธีที่คุณสามารถหลีกเลี่ยงได้
ค่าคงที่คืออะไร
โพสต์นี้เกี่ยวกับโมดูล แต่เพื่อให้เข้าใจโมดูลเหล่านี้ เราจำเป็นต้องเข้าใจค่าคงที่ ในภาษาส่วนใหญ่ ค่าคงที่ใช้เพื่อเก็บข้อมูลเพียงเล็กน้อยเท่านั้น ดังตัวอย่างด้านล่าง:
# These are simple constants
MAX_RETRIES = 5
DEFAULT_LANGUAGE = "en"
แต่ใน Ruby คลาสและโมดูลก็เป็นค่าคงที่เช่นกัน ฉันได้เขียนตัวอย่างเล็กๆ น้อยๆ เพื่อแสดงสิ่งนี้ มีสามค่าคงที่ในโมดูลด้านล่าง:ตัวเลข คลาส และโมดูลที่ซ้อนกัน เมื่อคุณเข้าถึงคลาสหรือโมดูลที่ซ้อนกัน ruby จะค้นหาโดยใช้กฎเดียวกันกับที่ใช้กับค่าคงที่ตัวเลขอย่างง่าย
module MyModule
MY_FAVORITE_NUMBER = 7
# Classes are constants
class MyClass
end
# So are modules
module MyEmbeddedModule
end
end
puts MyModule.constants.inspect # => [:MY_FAVORITE_NUMBER, :MyClass, :MyEmbeddedModule]
บ่อยครั้ง โมดูลสามารถเข้าถึงค่าคงที่ที่กำหนดไว้ในพาเรนต์ คุณจึงเขียนโค้ดได้ดังนี้:
module X
MARCO = "polo"
module Y
def self.n
puts MARCO
end
end
end
X::Y.n() # => "polo"
แต่คุณจะคิดผิดถ้าคุณคิดว่าความสัมพันธ์ระหว่างพ่อแม่และลูกเป็นสิ่งที่ช่วยให้ Y เข้าถึง MARCO คงที่ของ X ได้
ปัญหาทั่วไป
หากเราเขียนโค้ดด้านบนใหม่ในลักษณะที่ต่างออกไปเล็กน้อย สิ่งที่น่าประหลาดใจก็เกิดขึ้น Y ไม่สามารถเข้าถึง X::MARCO ได้อีกต่อไป เกิดอะไรขึ้นที่นี่?
module A
MARCO = "polo"
end
module A::B
def self.n
puts MARCO # => uninitialized constant A::B::MARCO (NameError)
end
end
A::B.n()
ปรากฎว่า "การสืบทอด" ของค่าคงที่ของผู้ปกครองโดยเด็กไม่ได้เกิดจากความสัมพันธ์ระหว่างผู้ปกครองและเด็ก มันเป็นศัพท์ ซึ่งหมายความว่าจะขึ้นอยู่กับโครงสร้างของโค้ด ไม่ใช่โครงสร้างของออบเจกต์ที่โค้ดของคุณสร้างขึ้น
กำลังตรวจสอบการทำรัง
หากคุณต้องการทำความเข้าใจให้ลึกซึ้งยิ่งขึ้นว่า Ruby ค้นหาค่าคงที่ที่ซ้อนกันอย่างไร คุณควรตรวจสอบ Module.nesting
ฟังก์ชัน
ฟังก์ชันนี้ส่งคืนอาร์เรย์ของออบเจ็กต์ที่ประกอบเป็น "เส้นทางการค้นหา" สำหรับค่าคงที่ในขอบเขตที่กำหนด มาตรวจสอบรังสำหรับตัวอย่างก่อนหน้าของเรากัน
ในตัวอย่างแรกของเรา เราจะเห็นว่าการซ้อนคือ [A::B, A]
. ซึ่งหมายความว่าหากเราใช้ค่าคงที่ MARCO Ruby จะค้นหามันก่อนใน A::B
แล้วใน A
.
module A
MARCO = "polo"
module B
def self.n
puts Module.nesting.inspect # => [A::B, A]
puts MARCO # => "polo"
end
end
end
ในตัวอย่างที่สอง เราจะเห็นว่าการซ้อนมีเพียง A::B
ไม่ใช่ A
. แม้ว่า B จะเป็น "ลูก" ของ A
วิธีที่ฉันเขียนโค้ดไม่ได้แสดงว่าซ้อนกัน ดังนั้นเพื่อจุดประสงค์นี้ โค้ดอาจไม่แสดงด้วยเช่นกัน
module A
MARCO = "polo"
end
module A::B
def self.n
puts Module.nesting.inspect # => [A::B]
puts MARCO # => uninitialized constant A::B::MARCO (NameError)
end
end
ปัญหาการโหลดอัตโนมัติของ Rails
คุณเคยสังเกตไหมว่าคุณไม่จำเป็นต้องรวมไฟล์เมื่อคุณใช้ Rails? หากคุณต้องการใช้โมเดล คุณก็แค่ใช้มัน
สิ่งนี้เป็นไปได้เพราะ Rails ใช้ระบบโหลดอัตโนมัติ มันใช้ Module.const_missing
เพื่อตรวจจับเมื่อคุณพยายามอ้างอิงค่าคงที่ที่ยังไม่ได้โหลด จากนั้นจะโหลดไฟล์ที่เชื่อว่าควรมีค่าคงที่ วิธีนี้ใช้ได้เกือบทุกครั้ง แต่ก็มีสิ่งที่จับได้
Rails ถือว่าโมดูลมีการซ้อนที่ใหญ่ที่สุดเสมอ สมมติว่าโมดูล A::B::C จะมี [A::B::C, A::B, A]
ซ้อนกัน . หากไม่เป็นเช่นนั้น คุณจะมีพฤติกรรมที่ไม่คาดคิด
ในโค้ดด้านล่าง โมดูล B ไม่ควรเข้าถึง A::MARCO
. ใน Ruby ปกติ มันทำไม่ได้เพราะการซ้อนของมันคือ [A::B] ดังนั้นคุณควรได้รับข้อยกเว้น แต่การโหลดอัตโนมัติของ Rails นั้นไม่มีข้อยกเว้น แต่จะส่งคืน A::MARCO
. แทน .
# a.rb
module A
MARCO = "polo"
end
# a/b.rb
module A::B
def self.n
puts MARCO # => "polo"
end
end
# some_controller.rb
A::B.n()
สรุป?
ทั้งหมดนี้เป็นเรื่องที่ต้องคิดมาก ฉันชอบที่จะหลีกเลี่ยงการคิดเมื่อเป็นไปได้ ดังนั้นฉันจึงพยายามอยู่ห่างจากโมดูล A::B
ไวยากรณ์ ฉันไม่สามารถนึกถึงกรณีที่ฉันต้องการจัดการการซ้อนโมดูลโดยเจตนา หากคุณรู้จัก ฉันชอบที่จะได้ยินเกี่ยวกับพวกเขา!