আজকের পোস্টে, আমরা রুবির #dup এবং #clone-এ নজর দিব। আমরা একটি রিয়েল-লাইফ উদাহরণ দিয়ে শুরু করব যেটি এই আগ্রহের কারণ বनी। তারপর, আমরা গভীরে যাব লক্ষ্যে #dup রুবিতে কীভাবে ইমপ্লিমেন্টেড এবং কীভাবে এটা #clone-এর সাথে তুলনা করে। আমরা শেষ করব আমাদের নিজস্ব #dup মেথড ইমপ্লিমেন্ট করে। চলুন!
আমি কীভাবে Dup ব্যবহার শুরু করলাম
যখন আমি একটি কোম্পানিতে কাজ করতাম যা NGO-র জন্য ক্যামপেইন সেট আপ করতে স্পেশালাইজড ছিল ডোনেশন কালেক্ট করার জন্য, আমাকে নিয়মিত ক্যামপেইন কপি করতে হতো এবং নতুন তৈরি করতে হতো। উদাহরণস্বরূপ, ২০১৮ ক্যামপেইন শেষ হওয়ার পর, ২০১৯ এর জন্য একটি নতুন ক্যামপেইন দরকার হতো।
একটি ক্যামপেইন সাধারণত ধারণ করতো খুব সার কনফিগারেশন অপশন, যেগুলো আমি আবার সেট আপ করতে আগ্রহী ছিলাম না। এটাতে অনেক সময় লাগতো এবং এরর-প্রোন ছিল। তাই আমি ডিবি রেকর্ড কপি করে শুরু করলাম।
প্রথম কয়েকটি ক্যামপেইন için, আমি আসলে সেটা হাত দিয়ে কপি করতাম। সেটা এমন দেখতো:
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; }
আমাদের জন্য, দিলচস্প অংশ হলো লাইন ১১-এ যেখানে রুবি ইনিশিয়ালাইজার মেথড #intialize_dup কল করে।
rb_funcallএকটি ফাংশন যা রুবি C কোডে ব্যাপকভাবে ব্যবহৃত হয়। এটি একটি অবজেক্টে মেথড কল করতে ব্যবহৃত হয়। এই কেসে এটি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; }
যদিও আরও কোড আছে, কিছু স্পেশাল হচ্ছে না ছাড়া কিছু চেক যা ইন্টারন্যালি রিকোয়ার্ড (বট ذات হয়তো অন্য সময়ের জন্য ভালো বিষয়)।
সুতরাং ইমপ্লিমেন্টেশনে কী হচ্ছে সেটা হলো রুবি আপনাকে একটি এন্ডপয়েন্ট দেয় এবং আপনার নিজস্ব ইন্টারেস্টিং আচরণ ইমপ্লিমেন্ট করার টুলস দেয়।
Rails-এর 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
টাইমস্ট্যাম্পগুলো Timestamp মডিউলে ক্লিয়ার হয়। এটি রেলসকে বলে সকল টাইমস্ট্যাম্প ক্লিয়ার করতে যা রেলস ব্যবহার করে ক্রিয়েট এবং আপডেটের জন্য (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 মেথডের বদলে। কেন এটি করবে? আসুন তদন্ত করি।
Object#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 ব্যবহার করে আপনাকে একটি রেকর্ড ডুপ্লিকেট করতে দেয় এর "ইন্টারন্যাল" স্টেট (id এবং টাইমস্ট্যাম্প) ছাড়া, এবং #clone-কে রুবির ইমপ্লিমেন্টেশনে ছেড়ে দেয়।
এই এক্সট্রা মেথড থাকার কারনও #clone মেথড ব্যবহার করার সময় একটি স্পেসিফিক ইনিশিয়ালাইজারের জন্য দাবি করে। এর জন্য, আপনি #initialize_clone ওভাররাইড করতে পারেন। এই মেথডটি #initialize_dup-এর মত লাইফসাইকেল ব্যবহার করে এবং #initialize_copy-কে কল করে।
এটি জানলে, ইনিশিয়ালাইজার মেথডগুলোর নামকরণ কিছুটা বেশি সেন্স মেক করে। আমরা #initialize_(dup|clone) ব্যবহার করতে পারি স্পেসifik ইমপ্লিমেন্টেশনের জন্য নির্ভর করে আপনি #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 একই থাকে, এবং এর নাম হবে মূল নামের সাথে ২ অ্যাপেন্ড করা।
- যখন আমরা প্রাণী ডুপ্লিকেট করি, আমরা মূল একটির ভিত্তিতে একটি নতুন প্রাণী তৈরি করি। এটিকে নিজস্ব DNA এবং একটি নতুন নাম मिलে।
- সকল কেসে প্রাণী বেবি হিসেবে শুরু করে।
আমরা তিনটি আলাদা ইনিশিয়ালাইজার ইমপ্লিমেন্ট করেছি এটা হাপেন করার জন্য। #initialize_(dup|clone) মেথডটি সবসময় #initialize_copy-কে কল করে, এইভাবে নিশ্চিত করে যে age ০ সেট করা হয়েছে।
CLONES এবং অন্যান্য প্রাণীদের মোড়া
আমরা যেটি খুঁজে বের করতে চেয়েছিলাম সেটা ব্যাখ্যা করার সাথে শুরু করে, আমরা ডেটাবেস রেকর্ড কপি করার দিকে নজর দিয়েছি। আমরা ক্যাম্পেইন উদাহরণে হাত থেকে কপি করার থেকে #dup এবং #clone-এ এসেছি। আমরা তারপর এটিকে প্র্যাক্টিক্যাল থেকে আকর্ষণীয়ে নিয়ে গেলাম এবং দেখেছি রুবিতে এটি কীভাবে ইমপ্লিমেন্টেড। আমরা #clone এবং #dup প্রাণীদের সাথে খেললাম। আশা করি আপনি আমাদের গভীর ডুব উপভোগ করেছেন যতটুকু আমরা লিখতে উপভোগ করেছি।