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
. Theclone
method does the same thing asdup
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
Error "Undefinded Method "Load_Defaults" " When Trying to Deploy App on Heroku
Could Not Find Rake-10.1.0 in Any of the Sources
Yielding in an Anonymous Block
/Usr/Bin/Env Ruby_Noexec_Wrapper Fails with No File or Directory
Rails: How to Check If a Column Has a Value
In Ruby or Rails, Why Is "Include" Sometimes Inside the Class and Sometimes Outside the Class
Ruby Conditional-Assignment and Private Methods
Rails Devise - Current_User Is Nil
How to Change Passenger Ruby Version Without Recompiling
Ruby Refuses to Divide Correctly
Get, or Calculate the Entropy of an Image with Ruby and Imagemagick
Instance Variable, Class Variable and the Difference Between Them in Ruby
In Rails, How to Access Response.Body in a Action Before It Returns
How to Split a String into Consecutive Substrings of Length at Most 3 in All Possible Ways
Rails: Undefined Method 'Truncate' in Model
How to Add "Somewhere" a 'Before(:Each)' Hook So That All Spec File Can Run It