आज की पोस्ट में, हम रूबी के #dup
. पर गौर करेंगे और #clone
. हम एक वास्तविक जीवन के उदाहरण से शुरू करेंगे जिसने इस रुचि को ट्रिगर किया। उसके बाद, हम #dup
. सीखने के लक्ष्य के साथ और गहराई में जाएंगे रूबी में लागू किया गया है और इसकी तुलना #clone
. से कैसे की जाती है . इसके बाद हम अपना #dup
. लागू करके इसे समाप्त कर देंगे तरीका। चलो चलें!
मैंने Dup का उपयोग कैसे शुरू किया
जब मैंने एक ऐसी कंपनी में काम किया, जो एनजीओ के लिए चंदा इकट्ठा करने के लिए अभियान स्थापित करने में विशेषज्ञता रखती थी, तो मुझे नियमित रूप से अभियानों की नकल करनी पड़ती थी और नए अभियान बनाने पड़ते थे। उदाहरण के लिए, 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!
यह आईडी और टाइमस्टैम्प को nil
. पर सेट कर देगा , ठीक यही हम हासिल करना चाहते हैं।
इस तरह मैं पहली बार #dup
. का उपयोग करने लगा . अब, आइए और गहराई से देखें कि कैसे #dup
वास्तव में काम करता है।
क्या चल रहा है अंडर द हुड?
#dup
. का डिफ़ॉल्ट रूबी कार्यान्वयन विधि आपको अपने ऑब्जेक्ट में एक विशेष इनिशियलाइज़र जोड़ने की अनुमति देती है जिसे केवल तब कहा जाता है जब किसी ऑब्जेक्ट को #dup
के माध्यम से इनिशियलाइज़ किया जाता है। तरीका। ये तरीके हैं:
initialize_copy
initialize_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
एक फ़ंक्शन है जिसका उपयोग रूबी सी कोड में बहुत अधिक किया जाता है। इसका उपयोग किसी वस्तु पर विधियों को कॉल करने के लिए किया जाता है। इस मामले में यह id_init_dup
. को कॉल करेगा 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;
}
अधिक कोड होने के बावजूद, आंतरिक रूप से आवश्यक कुछ जांचों को छोड़कर कुछ विशेष नहीं हो रहा है (लेकिन यह एक और समय के लिए एक अच्छा विषय हो सकता है)।
तो कार्यान्वयन में क्या होता है कि रूबी आपको एक समापन बिंदु देता है और आपको अपने दिलचस्प व्यवहार को लागू करने के लिए आवश्यक उपकरण प्रदान करता है।
रेल का डुप कार्यान्वयन
रेल ने कई स्थानों पर ठीक यही किया है, लेकिन अभी के लिए, हम केवल इस बात में रुचि रखते हैं कि कैसे 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_dup
. को ओवरराइड करना चुना है #initialize_copy
. के बजाय विधि तरीका। ऐसा क्यों करेगा? आइए जांच करते हैं।
ऑब्जेक्ट#इनिशियलाइज़_कॉपी समझाया गया
उपरोक्त कोड स्निपेट में, हमने देखा कि रूबी कैसे #initialize_dup
. को कॉल करती है जब आप .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
. पर कॉल करें . अगर हम कॉल को super
. पर रखते #initialize_dup
. से बाहर विधि, हमने कभी भी initialize_copy
. नहीं कहा होगा , इसलिए इसे अंदर रखना महत्वपूर्ण है।
क्या कुछ कॉपी करने के अन्य तरीके हैं?
अब जब हमने इस कार्यान्वयन को देख लिया है, तो आप सोच रहे होंगे कि दो #initialize_*
होने का क्या उपयोग है? तरीके। उत्तर है:वस्तुओं को कॉपी करने का एक और तरीका है, जिसे #clone
. कहा जाता है . आप आमतौर पर #clone
. का उपयोग करते हैं यदि आप किसी वस्तु को उसकी आंतरिक स्थिति सहित कॉपी करना चाहते हैं।
रेल अपने #dup
. के साथ इसका उपयोग कर रहा है ActiveRecord पर विधि। यह #dup
. का उपयोग करता है आपको किसी रिकॉर्ड को उसकी "आंतरिक" स्थिति (आईडी और टाइमस्टैम्प) के बिना डुप्लिकेट करने की अनुमति देने के लिए, और #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
. नामक एक वर्ग है , और इस पर निर्भर करते हुए कि हम जानवर की नकल कैसे करते हैं, उसका व्यवहार अलग होना चाहिए:
- जब हम जानवर का क्लोन बनाते हैं, तो डीएनए वही रहता है, और उसका नाम मूल नाम होगा जिसमें 2 संलग्न होंगे।
- जब हम जानवर की नकल करते हैं, तो हम मूल जानवर के आधार पर एक नया जानवर बनाते हैं। इसे अपना डीएनए और एक नया नाम मिलता है।
- सभी मामलों में जानवर एक बच्चे के रूप में शुरू होता है।
ऐसा करने के लिए हमने तीन अलग-अलग प्रारंभकर्ता लागू किए। #initialize_(dup|clone)
विधि हमेशा #initialize_copy
तक कॉल करेगी , इस प्रकार यह सुनिश्चित करता है कि आयु 0 पर सेट है।
क्लोन और अन्य जानवरों को गोल करना
खुजली की व्याख्या करके शुरू करते हुए हमें खुद को खरोंचने की जरूरत है, हमने डेटाबेस रिकॉर्ड की प्रतिलिपि बनाने पर ध्यान दिया। हम अभियान उदाहरण में हाथ से कॉपी करने से #dup
. पर गए और #clone
. फिर हमने इसे व्यावहारिक से आकर्षक में ले लिया और देखा कि रूबी में इसे कैसे लागू किया जाता है। हमने #clone
. के साथ भी खेला आईएनजी और #dup
जानवरों में। हमें उम्मीद है कि आपने हमारे गहरे गोता लगाने का उतना ही आनंद लिया जितना हमने इसे लिखा था।