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

หลีกเลี่ยงกับดักเหล่านี้เมื่อทำการซ้อนโมดูล Ruby

โมดูล (และคลาส) มีไว้เพื่อซ้อนกัน เศษโค้ด เช่น 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 ไวยากรณ์ ฉันไม่สามารถนึกถึงกรณีที่ฉันต้องการจัดการการซ้อนโมดูลโดยเจตนา หากคุณรู้จัก ฉันชอบที่จะได้ยินเกี่ยวกับพวกเขา!