Which Ruby Classes Support .Clone

What's the difference between Ruby's dup and clone methods?

Subclasses may override these methods to provide different semantics. In Object itself, there are two key differences.

First, clone copies the singleton class, while dup does not.

o = Object.new
def o.foo
42
end

o.dup.foo # raises NoMethodError
o.clone.foo # returns 42

Second, clone preserves the frozen state, while dup does not.

class Foo
attr_accessor :bar
end
o = Foo.new
o.freeze

o.dup.bar = 10 # succeeds
o.clone.bar = 10 # raises RuntimeError

The Rubinius implementation for these methods
is often my source for answers to these questions, since it is quite clear, and a fairly compliant Ruby implementation.

When to use dup, and when to use clone in Ruby?

It is true that clone copies the frozen state of an object, while dup does not:

o = Object.new
o.freeze

o.clone.frozen?
#=> true

o.dup.frozen?
#=> false

clone will also copy the singleton methods of the object while dup does not:

o = Object.new
def o.foo
42
end

o.clone.respond_to?(:foo)
#=> true

o.dup.respond_to?(:foo)
#=> false

Which leads me to the assumption that clone is sometimes understood as to provide a "deeper" copy than dup. Here are some quotes about the topic:

Comment on ActiveRecord::Base#initialize_dup from Rails 3:

Duped objects have no id assigned and are treated as new records. Note
that this is a "shallow" copy as it copies the object's attributes
only, not its associations. The extent of a "deep" copy is application
specific and is therefore left to the application to implement according
to its need.

An article about deep copies in Ruby:

There is another method worth mentioning, clone. The clone method does the same thing as dup with one important distinction: it's expected that objects will override this method with one that can do deep copies.

But then again, theres deep_dup in Rails 4:

Returns a deep copy of object if it's duplicable. If it's not duplicable, returns self.

and also ActiveRecord::Core#dup and #clone in Rails 4:

clone — Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied. [...] If you need a copy of your attributes hash, please use the #dup method.

Which means that here, the word dup is used to refer to a deep clone again. As far as I can see, there seems to be no consensus in the community, except that you should use clone and dup in the case when you need a specific side effect of either one.

Finally, I see dup much more often in Ruby code than clone. I have never used clone so far, and I won't until I explicitly need to.

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.

What's the difference between Rails dup and clone methods?

In rails 3.0, dup and clone performed essentially opposite roles as to what they do now. From ActiveRecord::Base:

Cloned objects have no id assigned and are treated as new records. Note that this is a "shallow" clone as it copies the object's attributes only, not its associations. The extent of a "deep" clone is application specific and is therefore left to the application to implement according to its need.

While it can be seen in the same file that dup simple copied the record and its attributes:

  def dup
obj = super
obj.instance_variable_set('@attributes', @attributes.dup)
obj
end

This differs from current rails 4, which defines dup and clone to more follow the note from the ruby docs, noted in a similar question not specific to rails.

In general, clone and dup may have different semantics in descendent classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendent object to create the new instance.

As can be seen from the from the more current ActiveRecord source:

##
# :method: clone
# Identical to Ruby's clone method. This is a "shallow" copy. Be
# warned that your attributes are not copied. That means that modifying
# attributes of the clone will modify the original, since they will both
# point to the same attributes hash. If you need a copy of your attributes
# hash, please use the #dup method.
#
# user = User.first
# new_user = user.clone
# user.name # => "Bob"
# new_user.name = "Joe"
# user.name # => "Joe"
#
# user.object_id == new_user.object_id # => false
# user.name.object_id == new_user.name.object_id # => true
#
# user.name.object_id == user.dup.name.object_id # => false

##
# :method: dup
# Duped objects have no id assigned and are treated as new records. Note
# that this is a "shallow" copy as it copies the object's attributes
# only, not its associations. The extent of a "deep" copy is application
# specific and is therefore left to the application to implement according
# to its need.
# The dup method does not preserve the timestamps (created|updated)_(at|on).

Cloning an array with its content

You need to do a deep copy of your array.

Here is the way to do it

Marshal.load(Marshal.dump(a))

This is because you are cloning the array but not the elements inside. So the array object is different but the elements it contains are the same instances. You could, for example, also do a.each{|e| b << e.dup} for your case

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

Clone (a.k.a. duplicate) a Record

Make sure the default cloned behavior works for you. the cloned record might actually be invalid according to your validation rules.

Try to use @item.save! instead of @item.save and check whether an exception is raised.
You can also try the code directly in a console instance.

In Console I figured out that clone generates the copy without ID.

That's true. #clone actually creates a clone but doesn't save the record.
This is why you need to call a save method in your action, which is what you actually do with

if @item.save # <-- here you save the record
flash[:notice] = 'Item was successfully cloned.'
else
flash[:notice] = 'ERROR: Item can\'t be cloned.'
end

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.

How do I clone a Rails model attribute?

I ended up making copies of each of the fields I wanted to keep track of:

@oldUsername = @user.username.clone

User.new looked promising, but it treated the copy as a new object, when it was an existing model, and output invalid forms to edit the model in the views:

> app.controller.view_context.form_for u2 do end   # This is from Rails console
=> "<form accept-charset=\"UTF-8\" action=\"/users\" class=\"new_user\" id=\"new_user_1\" method=\"post\">

So it would attempt to PATCH to /users (from the view), which is invalid, when it should PATCH to /users/1/.

It's unbelievable that Rails won't clone objects correctly. In Java, you could use u2.setProperty( u.getProperty().clone() ) and be sure to have a new object that won't interfere with the old one.

What's the most efficient way to deep copy an object in Ruby?

I was wondering the same thing, so I benchmarked a few different techniques against each other. I was primarily concerned with Arrays and Hashes - I didn't test any complex objects. Perhaps unsurprisingly, a custom deep-clone implementation proved to be the fastest. If you are looking for quick and easy implementation, Marshal appears to be the way to go.

I also benchmarked an XML solution with Rails 3.0.7, not shown below. It was much, much slower, ~10 seconds for only 1000 iterations (the solutions below all ran 10,000 times for the benchmark).

Two notes regarding my JSON solution. First, I used the C variant, version 1.4.3. Second, it doesn't actually work 100%, as symbols will be converted to Strings.

This was all run with ruby 1.9.2p180.

#!/usr/bin/env ruby
require 'benchmark'
require 'yaml'
require 'json/ext'
require 'msgpack'

def dc1(value)
Marshal.load(Marshal.dump(value))
end

def dc2(value)
YAML.load(YAML.dump(value))
end

def dc3(value)
JSON.load(JSON.dump(value))
end

def dc4(value)
if value.is_a?(Hash)
result = value.clone
value.each{|k, v| result[k] = dc4(v)}
result
elsif value.is_a?(Array)
result = value.clone
result.clear
value.each{|v| result << dc4(v)}
result
else
value
end
end

def dc5(value)
MessagePack.unpack(value.to_msgpack)
end

value = {'a' => {:x => [1, [nil, 'b'], {'a' => 1}]}, 'b' => ['z']}

Benchmark.bm do |x|
iterations = 10000
x.report {iterations.times {dc1(value)}}
x.report {iterations.times {dc2(value)}}
x.report {iterations.times {dc3(value)}}
x.report {iterations.times {dc4(value)}}
x.report {iterations.times {dc5(value)}}
end

results in:

user       system     total       real
0.230000 0.000000 0.230000 ( 0.239257) (Marshal)
3.240000 0.030000 3.270000 ( 3.262255) (YAML)
0.590000 0.010000 0.600000 ( 0.601693) (JSON)
0.060000 0.000000 0.060000 ( 0.067661) (Custom)
0.090000 0.010000 0.100000 ( 0.097705) (MessagePack)


Related Topics



Leave a reply



Submit