रेल्स 4.2 की घोषणा में आगामी रेल्स 5 के बारे में कुछ दिलचस्प समाचार थे: इसके लिए शायद रूबी 2.2 की आवश्यकता होगी। जो इसे रूबी 2 की सभी अच्छी चीजों का लाभ उठाने वाला पहला रेल्स वर्जन बना देगा।
पोस्ट में गार्बेज-कलेक्टेड सिम्बल्स और कीवर्ड आर्ग्यूमेंट्स का उल्लेख किया गया था। लेकिन मेरे लिए, रूबी 2 की सबसे दिलचस्प विशेषताओं में से एक Module#prepend है।
alias_method_chain का अच्छा (और बुरा)
जब मैंने पहली बार रेल्स सीखा, तो alias_method_chain ने मेरा दिमाग पूरी तरह से उड़ा दिया। इसने वास्तव में दिखाया कि रूबी कितना लचीला हो सकता है।
सिर्फ कोड की एक लाइन के साथ, आप पूरी तरह से बदल सकते हैं कि एक मेथड कैसे काम करता है। आपको अपने इच्छित कोड को जोड़ने के लिए लाइब्रेरीज को हैक करने की आवश्यकता नहीं थी, आप बस इसे ऑन-द-फ्लाई जोड़ सकते थे। alias_method_chain ने मेरे जेम्स में पहले पैच का नेतृत्व किया, जिससे मेरे पहले पुल रिक्वेस्ट बने, जिससे मेरे पहले ओपन सोर्स योगदान बने।
लेकिन मंकी पैचिंग की तरह ही, alias_method_chain का अत्यधिक उपयोग किया गया, और इसकी समस्याएं स्पष्ट होने लगीं:
- यह जो मेथड नाम उत्पन्न करता है वे भ्रमित करने वाले हैं, जिससे त्रुटियों को ढूंढना और डीबग करना कठिन हो जाता है। उदाहरण के लिए:
class Person
def greeting
"Hello"
end
end
module GreetingWithExcitement
def self.included(base)
base.class_eval do
alias_method_chain :greeting, :excitement
end
end
def greeting_with_excitement
"#{greeting_without_excitement}!!!!!"
end
end
Person.send(:include, GreetingWithExcitement)
यदि Person#greeting में कोई त्रुटि थी, तो बैकट्रेस आपको बताएगा कि त्रुटि वास्तव में Person#greeting_without_excitement में हुई। लेकिन वह मेथड कहां परिभाषित है? मैं इसे कहीं नहीं देखता। आप कैसे जानते हैं कि बग वाला greeting मेथड कौन सा है? और मेथड नाम जितना अधिक आप चेन करते हैं उतने ही भ्रमित करने वाले हो जाते हैं।
-
यदि आप एक ही क्लास पर समान पैरामीटर्स के साथ
alias_method_chainको दो बार कॉल करते हैं, तो आप स्टैक ओवरफ्लो का कारण बन सकते हैं। (क्या आप देख सकते हैं क्यों?) यह आमतौर पर नहीं होता है, जब तक आपकेrequireस्टेटमेंट्स उन पथों के बारे में सुसंगत हैं जिनका वे उपयोग करते हैं। लेकिन यह सुपर कष्टप्रद है यदि आप अक्सर रेल्स कंसोल में कोड पेस्ट करते हैं। -
और येहुदा काट्ज़ के ब्लॉग पोस्ट में वर्णित बाकी चीजें। इस पोस्ट ने बहुत सारे रेल्स डेवलपर्स को
alias_method_chainको छोड़कर मॉड्यूल इनहेरिटेंस के पक्ष में शुरू करने के लिए मना लिया।
तो, इसका अभी भी उपयोग क्यों किया जाता है?
आप अधिकांश alias_method_chain को उन मेथड्स को एक मॉड्यूल में ओवरराइड करके, और उन मॉड्यूल्स को अपनी चाइल्ड क्लासेस में शामिल करके बदल सकते हैं। लेकिन यह केवल तभी काम करता है जब आप अपने सुपरक्लास को ओवरराइड करना चाहते हैं, न कि अपनी क्लास को। अर्थात:
class ParentClass
def log
puts "In parent"
end
end
class ChildClass < ParentClass
def log
puts "In child"
super
end
def log_with_extra_message
puts "In child, with extra message"
log_without_extra_message
end
alias_method_chain :log, :extra_message
end
यदि आप ChildClass.new.log चलाते हैं, तो आप देखेंगे:
In child, with extra message
In child
In parent
यदि आप alias_method_chain के बजाय मॉड्यूल्स का उपयोग करने की कोशिश करते हैं, तो आप आउटपुट प्राप्त कर सकते हैं:
In child
In child, with extra message
In parent
लेकिन आप ChildClass में log मेथड को बदले बिना मूल आउटपुट से मेल नहीं खा सकते। रूबी इनहेरिटेंस उस तरह काम नहीं करता। खैर, यह नहीं करता था।
रूबी 2.0 में क्या बदला?
रूबी 2.0 तक, एक क्लास के नीचे कोड जोड़ने का कोई तरीका नहीं था, केवल ऊपर। लेकिन prepend के साथ, आप एक क्लास में मेथड को एक मॉड्यूल से मेथड से ओवरराइड कर सकते हैं, और अभी भी super के साथ क्लास के कार्यान्वयन तक पहुंच सकते हैं। तो, हमारे पिछले उदाहरण का उपयोग करके, हम मूल आउटपुट के साथ प्राप्त कर सकते हैं:
class ParentClass
def log
puts "In parent"
end
end
module ExtraMessageLogging
def log
puts "In child, with extra message"
super
end
end
class ChildClass < ParentClass
prepend ExtraMessageLogging
def log
puts "In child"
super
end
end
In child, with extra message
In child
In parent
परफेक्ट।
यदि prepend अभी भी आपके सिर के चारों ओर लपेटना कठिन है, तो इसे ऐसा करने के रूप में सोचें:
class NewChildClass < ChildClass
include ExtraMessageLogging
end
ChildClass = NewChildClass
सिवाय इसके कि यह आपके क्लास नामों के साथ खिलवाड़ नहीं करेगा, और यह पहले से मौजूद ऑब्जेक्ट्स को प्रभावित करता है।
(हां, आप रूबी में क्लास नामों को पुनः असाइन कर सकते हैं। नहीं, यह शायद एक महान विचार नहीं है।)
इसका रेल्स के लिए क्या मतलब है?
तो, alias_method_chain का उपयोग करने का अंतिम बहाना रूबी 2.0 में चला गया है। हम रेल्स में alias_method_chain के कुछ शेष उदाहरणों में से एक ले सकते हैं:
require 'active_support/core_ext/module/aliasing'
class Range
#:nodoc:
def each_with_time_with_zone(&block)
ensure_iteration_allowed
each_without_time_with_zone(&block)
end
alias_method_chain :each, :time_with_zone
def step_with_time_with_zone(n = 1, &block)
ensure_iteration_allowed
step_without_time_with_zone(n, &block)
end
alias_method_chain :step, :time_with_zone
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
और इसे एक मॉड्यूल से बदलें, बजाय:
require 'active_support/core_ext/module/aliasing'
module RangeWithTimeWithZoneSupport
#:nodoc:
def each(&block)
ensure_iteration_allowed
super(&block)
end
def step(n = 1, &block)
ensure_iteration_allowed
super(n, &block)
end
private
def ensure_iteration_allowed
if first.is_a?(Time)
raise TypeError, "can't iterate from #{first.class}"
end
end
end
Range.send(:prepend, RangeSupportingTimeWithZone)
यह क्लीनर है, Range#each का नाम नहीं बदला जाता, और ensure_iteration_allowed मंकी-पैच नहीं किया जाता।
पैच नहीं, इनहेरिटेंस का उपयोग करें
रूबी आपको बहुत लचीलापन देता है, और यही कारण है कि मैं इसे प्यार करता हूं। लेकिन इसमें एक शक्तिशाली ऑब्जेक्ट मॉडल भी है। इसलिए जब आप अपना कोड इंजेक्ट करना चाहते हैं, तो alias_method_chain जैसी किसी चीज़ में इसे हैक करने से पहले मॉड्यूल्स और इनहेरिटेंस पर झुकाव का प्रयास करें। आपका कोड समझने और डीबग करने में बहुत आसान होगा, और आप alias_method_chain जैसे किसी चीज़ के कठिन-से-पता-लगाने वाले साइड इफेक्ट्स से बचेंगे।
alias_method_chain सबसे अच्छे मेथड्स में से एक था जिससे मुझे रेल्स में परिचित कराया गया था। लेकिन इसके दिन गिने-चुने हैं। हम इससे आगे निकल चुके हैं। और जब यह चला जाएगा तो मुझे इसकी कमी नहीं खलेगी।