आज की पोस्ट में, हम रूबी के #dup और #clone को देखेंगे। हम एक वास्तविक जीवन के उदाहरण से शुरू करेंगे जिसने इस रुचि को ट्रिगर किया। उसके बाद, हम इस लक्ष्य के साथ गहराई से गोता लगाएंगे कि रूबी में #dup कैसे लागू किया जाता है और यह #clone की तुलना कैसे करता है। हम तब अपना स्वयं का #dup विधि लागू करके समाप्त करेंगे। चलते हैं!
मैंने Dup का उपयोग कैसे शुरू किया
जब मैंने एक कंपनी में काम किया जो दान एकत्र करने के लिए NGO के अभियानों को स्थापित करने में विशेषज्ञ थी, तो मुझे नियमित रूप से अभियानों की नकल करनी पड़ती थी और नए बनाने पड़ते थे। उदाहरण के लिए, 2018 अभियान समाप्त होने के बाद, 2019 के लिए एक नया आवश्यक था।
एक अभियान में आमतौर पर कॉन्फ़िगरेशन विकल्पों का भार होता था, जिन्हें मैं वास्तव में फिर से स्थापित करने का मन नहीं करता था। इसमें काफी समय लगेगा और त्रुटि-प्रवण था। इसलिए मैंने DB रिकॉर्ड को कॉपी करके शुरू किया और वहां से आगे बढ़ा।
पहले कुछ अभियानों के लिए, मैंने वास्तव में इसे हाथ से कॉपी किया। यह कुछ इस तरह दिखता था:
current_campaign = Campaign.find(1) new_campaign = current_campaign new_campaign.id = nil new_campaign.created_at = nil new_campaign.updated_at = nil new_campaign.title = "Campaign 2019" new_campaign.save!यह काम करता है, लेकिन इसमें बहुत टाइपिंग की आवश्यकता होती है, यह उल्लेख नहीं करने के लिए कि यह त्रुटि-प्रवण है। मैं अतीत में कुछ बार created_at को nil पर सेट करना भूल गया हूं।
चूंकि यह थोड़ा दर्द जैसा महसूस हुआ, मैं कल्पना नहीं कर सका कि यह इसके बारे में जाने का सबसे अच्छा तरीका था। और पता चला, एक बेहतर तरीका है!
new_campaign = Campaign.find(1).dup new_campaign.title = "Campaign 2019" new_campaign.save!यह ID और टाइमस्टैम्प्स को nil पर सेट करेगा, जो वास्तव में वही है जो हम पूरा करना चाहते हैं।
यही वह तरीका था जिससे मैंने पहली बार #dup का उपयोग करना शुरू किया। अब, आइए जाएं और इस बात पर गहरी नज़र डालें कि #dup वास्तव में कैसे काम करता है।
हुड के नीचे क्या हो रहा है?
#dup विधि का डिफ़ॉल्ट रूबी कार्यान्वयन आपको अपने ऑब्जेक्ट में एक विशेष इनिशियलाइज़र जोड़ने की अनुमति देता है जो केवल तब कॉल किया जाता है जब किसी ऑब्जेक्ट को #dup विधि के माध्यम से इनिशियलाइज़ किया जाता है। ये विधियाँ हैं:
initialize_copyinitialize_dup
इन विधियों का कार्यान्वयन वास्तव में काफी दिलचस्प है, क्योंकि वे डिफ़ॉल्ट रूप से कुछ नहीं करती हैं। वे मूल रूप से आपके लिए ओवरराइड करने के लिए प्लेसहोल्डर्स हैं।
यह सीधे रूबी स्रोत कोड से लिया गया है:
VALUE rb_obj_dup(VALUE obj) { VALUE dup; if (special_object_p(obj)) { return obj; } dup = rb_obj_alloc(rb_obj_class(obj)); init_copy(dup, obj); rb_funcall(dup, id_init_dup, 1, obj); return dup; }हमारे लिए, दिलचस्प भाग लाइन 11 पर है जहां रूबी इनिशियलाइज़र विधि #intialize_dup को कॉल करती है।
rb_funcallएक फ़ंक्शन है जिसका उपयोग रूबी C कोड में बहुत किया जाता है। इसका उपयोग किसी ऑब्जेक्ट पर विधियों को कॉल करने के लिए किया जाता है। इस मामले में यहdupऑब्जेक्ट परid_init_dupको कॉल करेगा।1बताता है कि कितने आर्गुमेंट्स हैं, इस मामले में केवल एक:obj
आइए थोड़ा और गहरा गोता लगाएं और उस कार्यान्वयन को देखें:
VALUE rb_obj_init_dup_clone(VALUE obj, VALUE orig) { rb_funcall(obj, id_init_copy, 1, orig); return obj; }जैसा कि आप इस उदाहरण में देख सकते हैं, id_init_copy को कॉल करने के अलावा वास्तव में कुछ भी नहीं हो रहा है। अब जब हम रैबिट होल में नीचे हैं, तो आइए उस विधि को भी देखें:
VALUE rb_obj_init_copy(VALUE obj, VALUE orig) { if (obj == orig) return obj; rb_check_frozen(obj); rb_check_trusted(obj); if (TYPE(obj) != TYPE(orig) || rb_obj_class(obj) != rb_obj_class(orig)) { rb_raise(rb_eTypeError, "initialize_copy should take same class object"); } return obj; }भले ही अधिक कोड है, आंतरिक रूप से आवश्यक कुछ चेक्स के अलावा कुछ खास नहीं हो रहा है (लेकिन यह किसी अन्य समय के लिए एक अच्छा विषय हो सकता है)।
तो कार्यान्वयन में क्या होता है कि रूबी आपको एक एंडपॉइंट देता है और आपके अपने दिलचस्प व्यवहार को लागू करने के लिए आवश्यक टूल्स प्रदान करता है।
रेल्स का Dup कार्यान्वयन
यह ठीक वही है जो रेल्स ने कई स्थानों पर किया था, लेकिन अभी के लिए, हम केवल इस बात में रुचि रखते हैं कि id और टाइमस्टैम्प फ़ील्ड्स कैसे क्लियर होते हैं।
ID को ActiveRecord के लिए कोर मॉड्यूल में क्लियर किया जाता है। यह ध्यान में रखता है कि आपका प्राइमरी की क्या है, इसलिए भले ही आपने इसे बदल दिया हो, यह अभी भी इसे रीसेट करेगा।
# activerecord/lib/active_record/core.rb def initialize_dup(other) # :nodoc: @attributes = @attributes.deep_dup @attributes.reset(self.class.primary_key) _run_initialize_callbacks @new_record = true @destroyed = false @_start_transaction_state = {} @transaction_state = nil super endटाइमस्टैम्प्स को टाइमस्टैम्प्स मॉड्यूल में क्लियर किया जाता है। यह रेल्स को उन सभी टाइमस्टैम्प्स को क्लियर करने के लिए कहता है जिनका उपयोग रेल्स बनाने और अपडेट करने के लिए कर सकता है (created_at, created_on, updated_at और updated_on)।
# activerecord/lib/active_record/timestamp.rb def initialize_dup(other) # :nodoc: super clear_timestamp_attributes endयहां एक दिलचस्प तथ्य यह है कि रेल्स ने जानबूझकर #initialize_copy विधि के बजाय #initialize_dup विधि को ओवरराइड करना चुना। यह ऐसा क्यों करेगा? आइए जांच करें।
Object#initialize_copy समझाया गया
ऊपर के कोड स्निपेट्स में, हमने देखा कि जब आप किसी मेथड पर .dup का उपयोग करते हैं तो रूबी #initialize_dup को कैसे कॉल करती है। लेकिन एक #initialize_copy विधि भी है। यह समझाने के लिए कि इसका उपयोग कहां किया जाता है, आइए एक उदाहरण देखें:
class Animal attr_accessor :name def initialize_copy(*args) puts "#initialize_copy is called" super end def initialize_dup(*args) puts "#initialize_dup is called" super end end animal = Animal.new animal.dup # => #initialize_dup is called # => #initialize_copy is calledअब हम देख सकते हैं कि कॉलिंग क्रम क्या है। रूबी पहले #initialize_dup को कॉल करती है और फिर #initialize_copy को कॉल करती है। यदि हम #initialize_dup विधि से super की कॉल को हटा देते, तो हम कभी initialize_copy को कॉल नहीं करते, इसलिए इसे बनाए रखना महत्वपूर्ण है।
क्या कॉपी करने के अन्य तरीके हैं?
अब जब हमने इस कार्यान्वयन को देखा है, तो आप सोच रहे होंगे कि दो #initialize_* विधियाँ होने का उपयोग केस क्या है। उत्तर है: ऑब्जेक्ट्स को कॉपी करने का एक और तरीका है, जिसे #clone कहा जाता है। आप आमतौर पर #clone का उपयोग करते हैं यदि आप किसी ऑब्जेक्ट को इसकी आंतरिक स्थिति सहित कॉपी करना चाहते हैं।
यही वह है जो रेल्स ActiveRecord पर अपनी #dup विधि के साथ उपयोग कर रहा है। यह #dup का उपयोग आपको इसकी "आंतरिक" स्थिति (id और टाइमस्टैम्प्स) के बिना एक रिकॉर्ड को डुप्लिकेट करने की अनुमति देने के लिए करता है, और #clone को रूबी पर लागू करने के लिए छोड़ देता है।
इस अतिरिक्त विधि के होने से #clone विधि का उपयोग करते समय एक विशिष्ट इनिशियलाइज़र की भी मांग होती है। इसके लिए, आप #initialize_clone को ओवरराइड कर सकते हैं। यह विधि #initialize_dup के समान जीवनचक्र का उपयोग करती है और #initialize_copy की ओर ऊपर कॉल करेगी।
यह जानकर, इनिशियलाइज़र विधियों का नामकरण थोड़ा अधिक समझ में आता है। हम #initialize_(dup|clone) का उपयोग विशिष्ट कार्यान्वयन के लिए कर सकते हैं यह इस बात पर निर्भर करता है कि आप #dup या #clone का उपयोग करते हैं। यदि हमारे पास व्यवहार है जो दोनों के लिए उपयोग किया जाता है, तो आप इसे #initialize_copy के अंदर रख सकते हैं।
एक जानवर को क्लोन करना
(सिर्फ एक उदाहरण, इस ब्लॉग पोस्ट के लिए कोई जानवर घायल नहीं हुआ)
अब आइए देखें कि यह अभ्यास में कैसे काम करता है।
class Animal attr_accessor :name, :dna, :age def initialize self.dna = generate_dna end def initialize_copy(original_animal) self.age = 0 super end def initialize_dup(original_animal) self.dna = generate_dna self.name = "A new name" super end def initialize_clone(original_animal) self.name = "#{original_animal.name} 2" super end def generate_dna SecureRandom.hex end end bello = Animal.new bello.name = "Bello" bello.age = 10 bello_clone = bello.clone bello_dup = bello.dup bello_clone.name # => "Bello 2" bello_clone.age # => 0 bello_dup.name # => "A new name" bello_dup.age # => 0आइए देखें कि वास्तव में क्या हो रहा है। हमारे पास Animal नामक एक क्लास है, और इस बात पर निर्भर करता है कि हम जानवर को कैसे कॉपी करते हैं, इसका व्यवहार अलग होना चाहिए:
- जब हम जानवर को क्लोन करते हैं, तो DNA वही रहता है, और इसका नाम मूल नाम होगा जिसमें 2 जोड़ा गया है।
- जब हम जानवर को डुप्लिकेट करते हैं, तो हम मूल के आधार पर एक नया जानवर बनाते हैं। इसे अपना DNA और एक नया नाम मिलता है।
- सभी मामलों में जानवर एक बच्चे के रूप में शुरू होता है।
हमने इसे बनाने के लिए तीन अलग-अलग इनिशियलाइज़र्स लागू किए। #initialize_(dup|clone) विधि हमेशा #initialize_copy तक ऊपर कॉल करेगी, इस प्रकार यह सुनिश्चित करते हुए कि उम्र 0 पर सेट है।
क्लोन्स और अन्य जानवरों को राउंड अप करना
हमारे द्वारा खरोंचने की आवश्यकता वाली खुजली को समझाकर शुरू करते हुए, हमने एक डेटाबेस रिकॉर्ड को कॉपी करने में देखा। हम अभियान उदाहरण में हाथ से कॉपी करने से, #dup और #clone तक गए। हमने फिर इसे व्यावहारिक से आकर्षक तक ले लिया और देखा कि यह रूबी में कैसे लागू किया जाता है। हमने #cloneिंग और #dupिंग जानवरों के साथ भी खेला। हम आशा करते हैं कि आपको हमारा गहरा गोता उतना ही पसंद आया जितना हमें इसे लिखने में आया।