Ruby dup/clone recursively
Here's how you make deep copies in Ruby
d = Marshal.load( Marshal.dump(h) )
Recursive function in ruby is overwriting nested attributes of cloned(object.dup) variable
Try using deep_dup
instead, your original code only dup
-ed the outermost hash.
final_entity = entity.deep_dup
clear_null_values(entity)
puts entity
puts final_entity
Outputs:
{2=>{4=>1}}
{1=>nil, 2=>{3=>nil, 4=>1}}
Note: Rails also adds Hash#compact
, which you could use to simplify clear_null_values
.
How can i duplicate a record with ruby (outside of rails)?
This will do a shallow copy of an object:
obj2 = obj.clone
This will do a deep copy of an object:
obj2 = Marshal.load(Marshal.dump(obj))
Ruby: How to use dup/clone to not mutate an original instance variable?
This is a common newbie mistake.
Suppose
a = [1, 2, 3]
b = a.dup
#=> [[1, 2], [3, 4]]
b[0] = 'cat'
#=> "cat"
b #=> ["cat", 2, 3]
a #=> [1, 2, 3]
This is exactly what you were expecting and hoping for. Now consider the following.
a = [[1, 2], [3, 4]]
b = a.dup
#=> [[1, 2], [3, 4]]
b[0] = 'cat'
b #=> ["cat", [3, 4]]
a #=> [[1, 2], [3, 4]]
Again, this is the desired result. One more:
a = [[1,2], [3,4]]
b = a.dup
#=> [[1,2], [3,4]]
b[0][0] = 'cat'
b #=> [["cat", 2], [3, 4]]
a #=> [["cat", 2], [3, 4]]
Aarrg! This is the problem that you experienced. To see what's happening here, let's look the id's of the various objects that make up a
and b
. Recall that every Ruby object has a unique Object#id.
a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
#=> [48959475855260, 48959475855240]
b.map(&:object_id)
#=> [48959475855260, 48959475855240]
b[0] = 'cat'
b #=> ["cat", [3, 4]]
a #=> [[1, 2], [3, 4]]
b.map(&:object_id)
#=> [48959476667580, 48959475855240]
Here we simply replace b[0]
, which initially was the object a[0]
with a different object ('cat'
) which of course has a different id. That does not affect a
. (In the following I will give just the last three digits of id's. If two are the same the entire id is the same.) Now consider the following.
a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
#=> [...620, ...600]
b.map(&:object_id)
#=> [...620, ...600]
b[0][0] = 'cat'
#=> "cat"
b #=> [["cat", 2], [3, 4]]
a #=> [["cat", 2], [3, 4]]
a.map(&:object_id)
#=> [...620, ...600]
b.map(&:object_id)
#=> [...620, ...600]
We see that the elements of a
and b
are the same objects as they were before executing b[0][0] = 'cat'
. That assignment, however, altered the value of the object whose id is ...620
, which explains why a
, as well as b
, was altered.
To avoid modifying a
we need to do the following.
a = [[1, 2], [3, 4]]
b = a.dup.map(&:dup) # same as a.dup.map { |arr| arr.dup }
#=> [[1, 2], [3, 4]]
a.map(&:object_id)
#=> [...180, ...120]
b.map(&:object_id)
#=> [...080, ...040]
Now the elements of b
are different objects than those of a
, so any changes to b
will not affect a
:
b[0][0] = 'cat'
#=> "cat"
b #=> [["cat", 2], [3, 4]]
a #=> [[1, 2], [3, 4]]
If we had
a = [[1, [2, 3]], [[4, 5], 6]]
we would need to dup
to three levels:
b = a.map { |arr0| arr0.dup.map { |arr1| arr1.dup } }
#=> [[1, [2, 3]], [[4, 5], 6]]
b[0][1][0] = 'cat'
b #=> [[1, ["cat", 3]], [[4, 5], 6]]
a #=> [[1, [2, 3]], [[4, 5], 6]]
and so on.
Original variable still changes with .clone or .dup
A dup
or clone
in Ruby is a shallow clone, meaning that only the outer object is cloned, not its children. In your case, this means that the couples
array is copied, but not each individual couple.
If you want it to be a deep clone, you need to do it manually for the stdlib arrays:
new_couples = original_couples.map { |couple| couple.clone }
If you are in a domain where copies of collections are often necessary, or you are trying to work in a more functional style, I suggest you take a look at the Hamster
gem, that brings immutable data structures to ruby.
How to create a deep copy of an object in Ruby?
Deep copy isn't built into vanilla Ruby, but you can hack it by marshalling and unmarshalling the object:
Marshal.load(Marshal.dump(@object))
This isn't perfect though, and won't work for all objects. A more robust method:
class Object
def deep_clone
return @deep_cloning_obj if @deep_cloning
@deep_cloning_obj = clone
@deep_cloning_obj.instance_variables.each do |var|
val = @deep_cloning_obj.instance_variable_get(var)
begin
@deep_cloning = true
val = val.deep_clone
rescue TypeError
next
ensure
@deep_cloning = false
end
@deep_cloning_obj.instance_variable_set(var, val)
end
deep_cloning_obj = @deep_cloning_obj
@deep_cloning_obj = nil
deep_cloning_obj
end
end
Source:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/43424
Which method to define on a Ruby class to provide dup / clone for its instances?
From my experience, overloading #initialize_copy works just fine (never heard about initialize_dup and initialize_clone).
The original initialize_copy (which initializes every instance variable with the values from the original object) is available through super, so I usually do:
class MyClass
def initialize_copy(orig)
super
# Do custom initialization for self
end
end
How do I copy all files recursively to a flat directory in Ruby?
You can try something like this:
def traverse (from, to)
Dir.chdir(from)
files = Dir.glob('*').select { |fn| File.file?(fn)}
FileUtils.cp files, to
subdirs = Dir.glob('*/')
subdirs.each do |subdir|
traverse subdir, to
end
Dir.chdir('..')
end
ruby: copy directories recursively with link dereferencing
Here's my implementation of find -follow
in ruby:
https://gist.github.com/akostadinov/05c2a976dc16ffee9cac
I could have isolated it into a class or monkey patch Find but I decided to do it as a self-contained method. There might be room for improvement because it doesn't work with jruby. If anybody has an idea, it will be welcome.
Update: found out why not working with jruby - https://github.com/jruby/jruby/issues/1895I'll try to workaround. I implemented a workaround.
Update 2: now cp_r_dereference
method ready - https://gist.github.com/akostadinov/fc688feba7669a4eb784
Related Topics
How to Express Infinity in Ruby
How to Skip Has_Secure_Password Validations
How to Have Rspec Test for My Default Scope
How to Deal with Memory Leaks in Rmagick in Ruby
Rubygems, Bundler and Rvm Confusion
Trouble Comparing Time with Rspec
Ruby Cannot Load Such File - Active_Support/Core_Ext/Object/Blank
How to Test Exception Raising in Rails/Rspec
Failing Installing Pg Gem, "Mkmf.Rb Can't Find Header Files for Ruby" (MAC Osx 10.6.5)
Selenium Scroll Element into (Center Of) View
How to Create a Nokogiri Case Insensitive Xpath Selector
How to Insert a String into a Textfile
Ruby: Intersection Between Two Ranges