Zeitwerk กับการตั้งชื่อคลาสที่ไม่ตรงกับเงื่อนไข

Published on
Tags
  • rails
  • zeitwerk
  • LFP

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 แหละ