मॉड्यूल्स (और क्लासेज) को नेस्ट करने के लिए बनाया गया है। ActiveRecord::RecordNotFound जैसे कोड फ्रैगमेंट्स इतने सामान्य हैं कि हम उनके बारे में दो बार नहीं सोचते। लेकिन Ruby के नेस्टिंग इम्प्लीमेंटेशन - और Rails के ऑटोलोड सिस्टम - के भीतर कुछ ट्रैप्स छिपे हुए हैं जो आपके कोड को अजीब और अद्भुत तरीकों से विफल कर सकते हैं। इस पोस्ट में, हम इन ट्रैप्स की उत्पत्ति और उनसे कैसे बचा जाए, इस पर चर्चा करेंगे।
कॉन्स्टेंट क्या है?
यह पोस्ट मॉड्यूल्स के बारे में है, लेकिन उन्हें समझने के लिए हमें कॉन्स्टेंट्स को समझने की आवश्यकता है। अधिकांश भाषाओं में, कॉन्स्टेंट्स का उपयोग केवल डेटा के छोटे टुकड़ों को स्टोर करने के लिए किया जाता है, जैसे नीचे दिए गए उदाहरण में:
# ये सरल कॉन्स्टेंट्स हैं
MAX_RETRIES = 5
DEFAULT_LANGUAGE = "en"
लेकिन Ruby में, क्लासेज और मॉड्यूल्स भी कॉन्स्टेंट्स हैं। मैंने इसे प्रदर्शित करने के लिए एक छोटा उदाहरण लिखा है। नीचे दिए गए मॉड्यूल में तीन कॉन्स्टेंट्स हैं: एक नंबर, एक क्लास और एक नेस्टेड मॉड्यूल। जब आप नेस्टेड क्लास या मॉड्यूल को एक्सेस करते हैं, तो Ruby उसे उन्हीं नियमों का उपयोग करके ढूंढता है जिनका उपयोग वह एक साधारण न्यूमेरिक कॉन्स्टेंट के साथ करेगा।
module MyModule
MY_FAVORITE_NUMBER = 7
# क्लासेज कॉन्स्टेंट्स हैं
class MyClass
end
# मॉड्यूल्स भी हैं
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 को X के कॉन्स्टेंट MARCO तक पहुंचने की अनुमति देता है।
एक सामान्य समस्या
यदि हम उपरोक्त कोड को थोड़े अलग तरीके से लिखते हैं, तो कुछ आश्चर्यजनक होता है। 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 सिंटैक्स से दूर रहने की कोशिश करता हूँ। मैं उस मामले के बारे में नहीं सोच सकता जहां मैं जानबूझकर मॉड्यूल नेस्टिंग में हेरफेर करना चाहूंगा। यदि आप किसी के बारे में जानते हैं, तो मुझे उनके बारे में सुनना अच्छा लगेगा!