अपना कैरी-ऑन लगेज पकड़ें, क्योंकि आज हम पूर्वज श्रृंखला में सबसे ऊपर तक यात्रा करेंगे। हम एक विधि कॉल का अनुसरण करेंगे और देखेंगे कि यह श्रृंखला में कैसे ऊपर जाती है और साथ ही पता लगाएंगे कि क्या होता है जब विधि गायब होती है। और चूंकि हम आग से खेलना पसंद करते हैं, हम वहां नहीं रुकेंगे बल्कि BasicObject#method_missing को ओवरराइड करने पर जारी रखेंगे। यदि आप ध्यान देते हैं, तो हम इसका एक व्यावहारिक उदाहरण में भी उपयोग कर सकते हैं। कोई गारंटी नहीं। चलते हैं!
पूर्वज श्रृंखला
आइए रूबी में पूर्वज श्रृंखला के मौलिक नियमों से शुरू करें:
- रूबी केवल एकल विरासत का समर्थन करती है
- यह एक ऑब्जेक्ट को मॉड्यूल्स के एक सेट को शामिल करने की भी अनुमति देती है
रूबी में, पूर्वज श्रृंखला किसी दिए गए क्लास के लिए विरासत में मिले सभी क्लासेस और मॉड्यूल्स के ट्रैवर्सल से बनी होती है।
आइए एक उदाहरण देखें जो दिखाता है कि रूबी में पूर्वज श्रृंखला को कैसे संभाला जाता है।
module Auth end module Session end module Iterable end class Collection prepend Iterable end class Users < Collection prepend Session include Auth end p Users.ancestorsउत्पादन करता है:
[ Session, Users, Auth, # Users Iterable, Collection, # Collection Object, Kernel, BasicObject # Ruby Object Model ]सबसे पहले, हम किसी दिए गए क्लास की पूर्वज श्रृंखला तक पहुंचने के लिए ancestors क्लास विधि को कॉल करते हैं।
हम देख सकते हैं कि Users.ancestors की कॉल क्लासेस और मॉड्यूल्स की एक सरणी लौटाती है जिनमें क्रम में होते हैं:
Usersके प्रीपेंडेड मॉड्यूल्सUsersक्लासUsersक्लास के शामिल मॉड्यूल्सCollectionक्लास के प्रीपेंडेड मॉड्यूल्स — Users के डायरेक्ट पैरेंट के रूप मेंCollectionक्लासCollectionक्लास के शामिल मॉड्यूल्स — कोई नहींObjectक्लास — किसी भी क्लास की डिफ़ॉल्ट विरासतKernelमॉड्यूल —Objectमें शामिल और कोर विधियों को धारण करने वालाBasicObjectक्लास — रूबी में रूट क्लास
तो, किसी दिए गए ट्रैवर्स्ड क्लास या मॉड्यूल के लिए उपस्थिति का क्रम हमेशा निम्नानुसार होता है:
- प्रीपेंडेड मॉड्यूल्स
- क्लास या मॉड्यूल
- इसके शामिल मॉड्यूल्स
पूर्वज श्रृंखला को मुख्य रूप से रूबी द्वारा तब ट्रैवर्स किया जाता है जब किसी ऑब्जेक्ट या क्लास पर कोई विधि लागू की जाती है।
विधि लुकअप पथ
जब एक संदेश भेजा जाता है, तो रूबी संदेश रिसीवर की पूर्वज श्रृंखला को ट्रैवर्स करती है और जांचती है कि क्या उनमें से कोई दिए गए संदेश का जवाब देता है।
यदि पूर्वज श्रृंखला का कोई दिया गया क्लास या मॉड्यूल संदेश का जवाब देता है, तो इस संदेश से जुड़ी विधि निष्पादित होती है और पूर्वज श्रृंखला ट्रैवर्सल रुक जाती है।
class Collection < Array end Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject] collection = Collection.new([:a, :b, :c]) collection.each_with_index # => :aयहां, collection.each_with_index संदेश Enumerable मॉड्यूल द्वारा प्राप्त किया जाता है। फिर इस संदेश के लिए Enumerable#each_with_index विधि को कॉल किया जाता है।
यहां, जब collection.each_with_index को कॉल किया जाता है, तो रूबी जांचती है कि:
- Collection
each_with_index messageका जवाब देता है => नहीं - Array
each_with_index messageका जवाब देता है => नहीं - Enumerable
each_with_index messageका जवाब देता है => हाँ
तो, यहां से, रूबी पूर्वज श्रृंखला ट्रैवर्सल को रोक देती है और इस संदेश से जुड़ी विधि को कॉल करती है। हमारे मामले में, Enumerable#each_with_index विधि।
रूबी में, इस तंत्र को विधि लुकअप पथ कहा जाता है।
अब, क्या होता है यदि दिए गए रिसीवर की पूर्वज श्रृंखला को बनाने वाले कोई भी क्लास और मॉड्यूल संदेश का जवाब नहीं देते?
BasicObject#method_missing
अब अच्छा बनना बंद! आइए चीजों को तोड़ें, डेवलपर स्टाइल में: अपवाद फेंककर। हम एक Collection क्लास लागू करेंगे और इसके एक इंस्टेंस पर एक अज्ञात विधि कॉल करेंगे।
class Collection end c = Collection.new c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>यहां, Collection क्लास search विधि को लागू नहीं करती। तो एक NoMethodError उठाया जाता है। लेकिन यह त्रुटि उठाना कहां से आता है?
त्रुटि BasicObject#method_missing विधि में उठाई जाती है। यह विधि तब कॉल की जाती है जब विधि लुकअप पथ दिए गए संदेश से मेल खाने वाली किसी भी विधि को खोजने में विफल रहता है।
ठीक है... लेकिन यह विधि केवल NoMethodError उठाती है। इसलिए इसे अपने Collection क्लास के संदर्भ में ओवरराइड करने में सक्षम होना बहुत अच्छा होगा।
BasicObject#method_missing विधि को ओवरराइड करना
अंदाजा लगाओ? method_missing को ओवरराइड करना पूरी तरह से ठीक है क्योंकि यह विधि विधि लुकअप पथ के तंत्र के अधीन भी है। एक सामान्य विधि के साथ एकमात्र अंतर यह है कि हम सुनिश्चित हैं कि यह विधि विधि लुकअप पथ द्वारा कम से कम एक बार पाई जाएगी।
वास्तव में, BasicObject class — जो रूबी में किसी भी क्लास की रूट क्लास है — इस विधि का एक न्यूनतम संस्करण परिभाषित करता है। क्लासिक रूबी मैजिक, न'est-ce pas?
तो आइए इस विधि को अपने Collection class में ओवरराइड करें:
class Collection def initialize @collection = {} end def method_missing(method_id, *args) if method_id[-1] == '=' key = method_id[0..-2] @collection[key.to_sym] = args.first else @collection[method_id] end end end collection = Collection.new collection.obj1 = 'value1' collection.obj2 = 'value2' collection.obj1 # => 'value1' collection.obj2 # => 'value2'यहां, Collection#method_missing @collection इंस्टेंस वेरिएबल के लिए एक डेलीगेटर के रूप में कार्य करता है। वास्तव में, यह वही तरीका है जिससे रूबी मोटे तौर पर ऑब्जेक्ट डेलीगेशन को संभालती है — cf: delegate लाइब्रेरी।
यदि गायब विधि एक सेटर विधि है (collection.obj1 = 'value1'), तो विधि नाम (:obj1) को कुंजी के रूप में और आर्गुमेंट ('value1') को @collection हैश एंट्री के मान के रूप में उपयोग किया जाता है (@collection[:obj1] = 'value1')।
एक HTML टैग जेनरेटर
अब जब हम जानते हैं कि method_missing विधि पर्दे के पीछे कैसे काम करती है, आइए एक पुनरुत्पादन योग्य उपयोग केस लागू करें।
यहां, लक्ष्य निम्नलिखित DSL को परिभाषित करना है:
HTML.p 'hello world' # => <p>hello world</p> HTML.div 'hello world' # => <div>hello world</div> HTML.h1 'hello world' # => <h1>hello world</h1> HTML.h2 'hello world' # => <h2>hello world</h2> HTML.span 'hello world' # => <span>hello world</span> HTML.p "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>ऐसा करने के लिए, हम प्रत्येक HTML टैग के लिए एक विधि को परिभाषित करने से बचने के लिए HTML.method_missing विधि को लागू करने जा रहे हैं।
सबसे पहले, हम एक HTML मॉड्यूल परिभाषित करते हैं। फिर हम इस मॉड्यूल में एक method_missing क्लास विधि परिभाषित करते हैं:
module HTML def HTML.method_missing(method_id, *args, &block) "<#{method_id}>#{args.first}</#{method_id}>" end endहमारी विधि केवल गायब method_id का उपयोग करके एक HTML टैग बनाएगी — उदाहरण के लिए HTML.div की कॉल के लिए :div।
ध्यान दें कि क्लास विधियां भी विधि लुकअप पथ के अधीन हैं।
हम अपने HTML टैग जेनरेटर को निम्न द्वारा बढ़ा सकते हैं:
- नेस्टेड टैग्स को संभालने के लिए ब्लॉक आर्गुमेंट का उपयोग करना
- सिंगल टैग्स को संभालना — उदाहरण के लिए
<br/>
लेकिन ध्यान दें कि कोड की कुछ पंक्तियों के साथ, हम बड़ी मात्रा में HTML टैग्स जेनरेट करने में सक्षम हैं।
तो, संक्षेप में:
method_missing उन DSLs को बनाने के लिए एक अच्छा प्रवेश बिंदु है जहां अधिकांश कमांड पहचाने गए पैटर्न के एक सेट को साझा करेंगे।
निष्कर्ष
हम रूबी में पूर्वज श्रृंखला में सबसे ऊपर तक गए और BasicObject#method_missing में गोता लगाया। BasicObject#method_missing रूबी हुक विधियों का हिस्सा है। इसका उपयोग ऑब्जेक्ट्स के साथ उनके जीवनचक्र में कुछ सटीक क्षणों में बातचीत करने के लिए किया जाता है। अन्य रूबी हुक विधियों की तरह, इस हुक विधि का सावधानी से उपयोग किया जाना चाहिए। और सावधानी से, हमारा मतलब है कि इसे कभी भी रूबी ऑब्जेक्ट मॉडल के व्यवहार को संशोधित नहीं करना चाहिए — सिवाय जब आप इसके साथ खेल रहे हों या इस पर एक ब्लॉग पोस्ट लिख रहे हों ;-)
Voilà!