Zeitwerk กับการตั้งชื่อคลาสที่ไม่ตรงกับเงื่อนไข
Rails ใช้ Zeitwerk
ในการโหลดคลาสและโมดูลแบบอัตโนมัติ (Autoloading) โดยที่เราไม่ต้องเรียกผ่าน require
เพียงแต่ต้องทำการเงื่อนไขที่ถูกต้อง เราสามารถไปศึกษารายละเอียดได้จากที่นี่
แต่เอาเป็นว่าหลักๆ คือชื่อไฟล์ที่ตั้งนั้นจะต้องสอดคล้องกับคลาสหรือโมดูลที่นิยามอยู่ในไฟล์ ยกตัวอย่างชื่อไฟล์กับนิยาม
- my_gem.rb ->
MyGem
- my_gem/foo.rb ->
MyGem::Foo
- my_gem/foo/bar.rb ->
MyGem::Foo:Bar
แน่นอนว่ามันก็จะมีรายละเอียดยิบย่อยอีกพอสมควร
ประเด็นที่เป็นปัญหาของผมก็เกิดขึ้นจากการที่พยายามจะเพิ่มความสามารถให้กับ Ruby Core Class อย่างเช่น String
, Date
ซึ่งได้มีการสร้างโฟลเดอร์ core_ext ไว้ใน app/lib
ของโปรเจ็ค โดยผมต้องการจะเพิ่มความสามารถให้กับ DateTime
เพื่อสามารถดึงวันที่สอดคล้องกับปีงบประมาณประจำปี
# app/lib/core_ext/date_time/budget_year.rb
class DateTime
def start_budget_year; end
def end_budget_year; end
end
จะเห็นได้ว่าถ้าเราจะเพิ่มความสามารถให้กับคลาสหรือโมดูลใด เราต้องตั้งชื่อคลาสหรือโมดูลให้ตรง แต่จะเห็นว่าชื่อไฟล์ที่ตั้งก็ไม่ตรงกับนิยามข้างใน อีกทั้งโฟลเดอร์ app/lib อยู่ภายใต้การตรวจสอบของ Zeitwerk เมื่อรันโปรแกรมขึ้นมาจะมี Zeitwerk::NameError
ออกมาทันที ดังนั้นวิธีแก้ไขที่ทำได้ก็คือปรับให้โฟลเดอร์ app/lib/core_ext เข้าไปอยู่ในการตรวจสอบของ Zeitwerk โดยการสร้างไฟล์ config/initializers/zeitwerk.rb ขึ้นมาและกำหนดให้โหลดโค้ดแบบ manual แทน
# config/initializers/zeitwerk.rb
Rails.autoloaders.main.ignore(Rails.root.join('app/lib/core_ext'))
Dir.glob(Rails.root.join('app/lib/core_ext', '*.rb')).sort.each { |path| require path }
เพียงเท่านี้เราก็สามารถโหลดส่วนเพิ่มเติมเข้าไปในโปรเจ็คได้ โดยที่ไม่ผิดกับเงื่อนไขของ Zeitwerk แหละ