🧬 Easily use the correct clone or dup method in initialize_copy.
If we only use either clone or dup or we use Marshal, then that does not produce the correct behavior according to the standard documentation. This is because clone should preserve the internal state (e.g., the frozen state and extended modules), while dup should not. For example:
module SecretExt
def secret
'password'
end
end
# Init vars.
bob = 'Bob'
bob.extend(SecretExt)
bob.freeze
clone = bob.clone
dup = bob.dup
# Check vars.
puts clone.frozen? #=> true
puts dup.frozen? #=> false
puts clone.secret #=> password
puts dup.secret #=> NoMethodErrorTo solve this issue in the past, we had to define both initialize_clone & initialize_dup and maintain two copies of all our deep-copy code. That sucks. 😞
🚀 Instead, InitCopy was created:
- Install the gem
init_copy(on RubyGems.org). - Include
InitCopy::Ablein your class/module. - Override the
init_copy(original)method. - Use
ic_copy(var), instead of clone/dup.
Example usage:
require 'init_copy'
class JangoFett
include InitCopy::Able
attr_reader :gear,:bounties,:order66
def initialize
super
@gear = ['blaster','jetpack']
@bounties = ['Padmé','Vosa']
@order66 = Class.new do
undef_method :clone
undef_method :dup
end.new
end
protected
def init_copy(_orig)
super
@gear = ic_copy(@gear)
@bounties = ic_copy(@bounties)
@order66 = ic_copy?(@order66) # Safe copy if no dup/clone.
end
end
# Init vars.
jango = JangoFett.new
jango.bounties.freeze
boba1 = jango.clone
boba2 = jango.dup
jango.gear << 'vibroblade'
boba1.gear << 'implant'
# Check vars.
puts jango.gear.inspect #=> ["blaster", "jetpack", "vibroblade"]
puts boba1.gear.inspect #=> ["blaster", "jetpack", "implant"]
puts boba2.gear.inspect #=> ["blaster", "jetpack"]
puts boba1.bounties.inspect #=> ["Padmé", "Vosa"]
puts boba2.bounties.inspect #=> ["Padmé", "Vosa"]
puts boba1.bounties.frozen? #=> true (clone)
puts boba2.bounties.frozen? #=> false (dup)// Setup
Pick your poison...
With the RubyGems CLI package manager:
gem install init_copyIn your Gemspec:
spec.add_dependency 'init_copy', '~> X.X'In your Gemfile:
# Pick your poison...
gem 'init_copy', '~> X.X'
gem 'init_copy', git: 'https://github.com/esotericpig/init_copy.git', branch: 'main'From source:
git clone --depth 1 'https://github.com/esotericpig/init_copy.git'
cd init_copy
bundle install
bundle exec rake install:local// Hacking
git clone 'https://github.com/esotericpig/init_copy.git'
cd init_copy
bundle install
bundle exec rake -TTesting:
bundle exec rake testGenerating doc:
bundle exec rake clobber_doc docInstalling:
bundle exec rake install:local// License
Copyright (c) 2020-2025 Bradley Whited
MIT License