🧬 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 #=> NoMethodError
To 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::Able
in 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_copy
In 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 -T
Testing:
bundle exec rake test
Generating doc:
bundle exec rake clobber_doc doc
Installing:
bundle exec rake install:local
// License
Copyright (c) 2020-2025 Bradley Whited
MIT License