Ruby Dup/Clone Recursively

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/1895
I'll try to workaround. I implemented a workaround.

Update 2: now cp_r_dereference method ready - https://gist.github.com/akostadinov/fc688feba7669a4eb784



Related Topics



Leave a reply



Submit