रेल्स एक बड़ा फ्रेमवर्क है, और यह हर साल बड़ा होता जा रहा है। इससे कुछ उपयोगी फीचर्स का किसी का ध्यान न जाना आसान हो जाता है। इस श्रृंखला में, हम रेल्स में विशिष्ट कार्यों के लिए अंतर्निहित कुछ कम-ज्ञात कार्यक्षमता पर एक नज़र डालेंगे।
इस श्रृंखला के पहले लेख में, हम यह देखने जा रहे हैं कि Rails.env.test? को कॉल करना वास्तव में ActiveSupport से कम-ज्ञात StringInquirer क्लास का उपयोग करके हुड के तहत कैसे काम करता है। एक कदम आगे जाकर, हम StringInquirer सोर्स कोड के माध्यम से भी चलेंगे कि यह कैसे काम करता है, जो (स्पॉइलर अलर्ट) रूबी के मेटाप्रोग्रामिंग का एक सरल उदाहरण है विशेष method_missing मेथड के माध्यम से।
Rails.env हेल्पर
आपने शायद पहले से ही कोड देखा है जो रेल्स एनवायरनमेंट की जांच करता है:
if Rails.env.test? # हार्ड-कोडेड मान वापस करें... else # एक्सटर्नल API से संपर्क करें... end हालांकि, test? वास्तव में क्या करता है, और यह मेथड कहां से आता है?
यदि हम कंसोल में Rails.env की जांच करते हैं, तो यह एक स्ट्रिंग की तरह व्यवहार करता है:
=> Rails.env "development" यहां स्ट्रिंग वही है जो RAILS*ENV एनवायरनमेंट वेरिएबल सेट है; यह _सिर्फ_ एक String नहीं है, हालांकि। कंसोल में क्लास पढ़ने पर, हम देखते हैं:
=> Rails.env.class ActiveSupport::StringInquirer Rails.env वास्तव में एक StringInquirer है।
StringInquirer
StringInquirer का कोड संक्षिप्त और अच्छी तरह से डॉक्यूमेंटेड है। यह कैसे काम करता है, हालांकि, स्पष्ट नहीं हो सकता है जब तक कि आप रूबी की मेटाप्रोग्रामिंग क्षमताओं से परिचित न हों। हम यहां क्या हो रहा है इसके माध्यम से चलेंगे।
class StringInquirer < String ... end सबसे पहले, हम देखते हैं कि StringInquirer String का एक सबक्लास है। यही कारण है कि जब हम इसे कॉल करते हैं तो Rails.env एक स्ट्रिंग की तरह व्यवहार करता है। String से इनहेरिट करके, हमें स्वचालित रूप से सभी स्ट्रिंग-जैसी कार्यक्षमता मिलती है, इसलिए हम इसे एक स्ट्रिंग की तरह ट्रीट कर सकते हैं। Rails.env.upcase काम करता है, जैसा कि ActiveRecordModel.find_by(string_column: Rails.env) करता है।
जबकि Rails.env StringInquirer का एक आसान, बिल्ट-इन उदाहरण है, हम अपने खुद के बनाने के लिए भी स्वतंत्र हैं:
def type result = "old" result = "new" if @new ActiveSupport::StringInquirer.new(result) end फिर, हमें वापस किए गए मान पर प्रश्न-चिह्न मेथड्स मिलते हैं:
=> @new = false => type.old? true => type.new? false => type.testvalue? false => @new = true => type.new? true => type.old? false method_missing
method_missing यहां असली सीक्रेट सॉस है। रूबी में, जब भी हम किसी ऑब्जेक्ट पर एक मेथड कॉल करते हैं, रूबी ऑब्जेक्ट के सभी पूर्वजों (यानी, बेस क्लासेस या शामिल मॉड्यूल्स) को मेथड के लिए देखना शुरू करता है। यदि कोई नहीं मिलता है, तो रूबी method_missing को कॉल करेगा, जिसमें उस मेथड का नाम पास किया जाएगा जिसे वह ढूंढ रहा है, साथ ही किसी भी आर्ग्यूमेंट्स के साथ।
डिफ़ॉल्ट रूप से, यह मेथड सिर्फ एक अपवाद उठाता है। हम सभी शायद NoMethodError: undefined method 'test' for nil:NilClass टाइप के एरर मैसेज से परिचित हैं। हम अपना खुद का method_missing इम्प्लीमेंट कर सकते हैं जो अपवाद नहीं उठाता, जो ठीक वही है जो StringInquirer करता है:
def method_missing(method_name, *arguments) if method_name.end_with?("?") self == method_name[0..-2] else super end end किसी भी मेथड नाम के लिए जो ? में समाप्त होता है, हम self के मान (यानी, स्ट्रिंग) की जांच मेथड नाम के बिना ? के विरुद्ध करते हैं। दूसरे शब्दों में, यदि हम StringInquirer.new("test").long_test_method_name? कॉल करते हैं, तो वापस किया गया मान "test" == "long_test_method_name" है।
यदि मेथड नाम नहीं प्रश्न चिह्न के साथ समाप्त होता है, तो हम मूल method_missing पर वापस आ जाते हैं (वह जो अपवाद उठाएगा)।
respond_to_missing?
फाइल में एक और मेथड है: respond_to_missing?. हम कह सकते हैं कि यह method_missing का एक साथी मेथड है। हालांकि method_missing हमें कार्यक्षमता देता है, हमें रूबी को यह बताने का एक तरीका भी चाहिए कि हम इन प्रश्न-चिह्न-समाप्त होने वाले मेथड्स को स्वीकार करते हैं।
def respond_to_missing?(method_name, include_private = false) method_name.end_with?("?") || super end यह तब काम आता है जब हम इस ऑब्जेक्ट पर respond_to? कॉल करते हैं। इसके बिना, यदि हम StringInquirer.new("test").respond_to?(:test?) कॉल करते हैं, तो परिणाम false होगा क्योंकि हमारे पास test? नामक कोई स्पष्ट मेथड नहीं है। यह स्पष्ट रूप से भ्रामक है क्योंकि मुझे उम्मीद होगी कि यह true वापस करेगा यदि मैं Rails.env.respond_to?(:test?) कॉल करता हूं।
respond_to_missing? वही है जो हमें रूबी से "हां, मैं उस मेथड को संभाल सकता हूं" कहने की अनुमति देता है। यदि मेथड नाम प्रश्न चिह्न में समाप्त नहीं होता है, तो हम सुपरक्लास मेथड पर वापस आ जाते हैं।
व्यावहारिक उपयोग के मामले
अब जब हम जानते हैं कैसे StringInquirer काम करता है, आइए उन उदाहरणों पर एक नज़र डालें जहां यह उपयोगी हो सकता है:
1. एनवायरनमेंट वेरिएबल्स
एनवायरनमेंट वेरिएबल्स दो मानदंडों को पूरा करते हैं जो उन्हें StringInquirer के लिए बेहतरीन विकल्प बनाते हैं। पहला, उनके पास अक्सर संभावित मानों का एक सीमित, ज्ञात सेट होता है (एक enum की तरह), और दूसरा, हमारे पास अक्सर सशर्त तर्क होता है जो उनके मान पर निर्भर करता है।
मान लें कि हमारा ऐप एक पेमेंट API से जुड़ा हुआ है, और हमारे पास क्रेडेंशियल्स एनवायरनमेंट वेरिएबल्स में स्टोर हैं। हमारे प्रोडक्शन सिस्टम में, यह स्पष्ट रूप से वास्तविक API है, लेकिन हमारे स्टेजिंग या डेवलपमेंट वर्जन में, हम उनके सैंडबॉक्स API का उपयोग करना चाहते हैं:
# ENV["PAYMENT_API_MODE"] = sandbox/production class PaymentGateway def api_mode # हम ENV.fetch का उपयोग करते हैं क्योंकि हम चाहते हैं कि यदि मान गायब है तो यह एरर उठाए @api_mode ||= ENV.fetch("PAYMENT_API_MODE").inquiry end def api_url # प्रो-टिप: हम *केवल* प्रोडक्शन का उपयोग करते हैं यदि MODE == 'production', और डिफ़ॉल्ट रूप से # सैंडबॉक्स पर अगर मान कुछ और है, यह हमें गलत टाइप या गलत मान होने पर प्रोडक्शन मानों का उपयोग करने से रोकता है if api_mode.production? PRODUCTION_URL else SANDBOX_URL end end end ध्यान दें कि उपरोक्त में, हम ActiveSupport के String#inquiry मेथड का उपयोग कर रहे हैं, जो आसानी से एक String को StringInquirer में परिवर्तित करता है।
2. API रिस्पॉन्स
ऊपर से हमारे पेमेंट API उदाहरण को जारी रखते हुए, API हमें एक रिस्पॉन्स भेजेगा जिसमें कुछ सफलता/विफलता स्थिति शामिल होगी। फिर से, यह उन दो मानदंडों को पूरा करता है जो इसे StringInquirer के लिए उम्मीदवार बनाते हैं: संभावित मानों का एक सीमित सेट और सशर्त तर्क जो इन मानों का परीक्षण करेगा।
class PaymentGateway def create_charge response = JSON.parse(api_call(...)) result = response["result"].inquiry if result.success? ... else ... end # result अभी भी एक स्ट्रिंग की तरह व्यवहार करता है Rails.logger.info("Payment result was: #{result}") end end निष्कर्ष
StringInquirer आपकी पिछली जेब में रखने के लिए एक दिलचस्प टूल है, लेकिन व्यक्तिगत रूप से, मैं इसके लिए बहुत बार नहीं पहुंचूंगा। इसके कुछ उपयोग हैं, लेकिन अधिकांश समय, किसी ऑब्जेक्ट पर एक स्पष्ट मेथड आपको वही परिणाम देता है। एक स्पष्ट रूप से नामित मेथड के भी कुछ लाभ हैं; यदि मान को कभी बदलने की आवश्यकता होती है, तो आपको केवल एक ही स्थान को अपडेट करना होगा, और यह कोडबेस को खोजने में आसान बनाता है यदि कोई डेवलपर मेथड का पता लगाने की कोशिश कर रहा है।
हालांकि यह StringInquirer पर केंद्रित है, यह लेख रूबी की कुछ मेटाप्रोग्रामिंग क्षमताओं का उपयोग करके method_missing के एक सौम्य परिचय के रूप में अधिक इरादा रखता है। मैं कहूंगा कि आप शायद अपने एप्लिकेशन में method_missing का उपयोग नहीं करना चाहते हैं। हालांकि, यह अक्सर फ्रेमवर्क्स में उपयोग किया जाता है, जैसे रेल्स या जेम्स द्वारा प्रदान किए गए डोमेन-स्पेसिफिक-लैंग्वेज, इसलिए जब आप मुद्दों का सामना करते हैं तो यह जानना बहुत मददगार हो सकता है कि "सॉसेज कैसे बनाया जाता है"।