@@ -10,7 +10,11 @@ module Copyable
10
10
# the exception of the document's id, and will reset all the
11
11
# instance variables.
12
12
#
13
- # This clone also includes embedded documents.
13
+ # This clone also includes embedded documents. If there is an _id field in
14
+ # the embedded document, it will be maintained, unlike the root's _id.
15
+ #
16
+ # If cloning an embedded child, the embedded parent is not cloned and the
17
+ # embedded_in association is not set.
14
18
#
15
19
# @example Clone the document.
16
20
# document.clone
@@ -19,29 +23,49 @@ module Copyable
19
23
def clone
20
24
# @note This next line is here to address #2704, even though having an
21
25
# _id and id field in the document would cause problems with Mongoid
22
- # elsewhere.
26
+ # elsewhere. Note this is only done on the root document as we want
27
+ # to maintain the same _id on the embedded documents.
23
28
attrs = clone_document . except ( *self . class . id_fields )
29
+ Copyable . clone_with_hash ( self . class , attrs )
30
+ end
31
+ alias :dup :clone
32
+
33
+ private
34
+
35
+ # Create clone of a document of the given klass with the given attributes
36
+ # hash. This is used recursively so that embedded associations are cloned
37
+ # safely.
38
+ #
39
+ # @param klass [ Class ] The class of the document to create.
40
+ # @param attrs [ Hash ] The hash of the attributes.
41
+ #
42
+ # @return [ Document ] The new document.
43
+ def self . clone_with_hash ( klass , attrs )
24
44
dynamic_attrs = { }
25
- _attribute_names = self . attribute_names
45
+ _attribute_names = klass . attribute_names
26
46
attrs . reject! do |attr_name , value |
27
47
unless _attribute_names . include? ( attr_name )
28
48
dynamic_attrs [ attr_name ] = value
29
49
true
30
50
end
31
51
end
32
- self . class . new ( attrs ) . tap do |object |
52
+
53
+ Factory . build ( klass , attrs ) . tap do |object |
33
54
dynamic_attrs . each do |attr_name , value |
34
- if object . respond_to? ( "#{ attr_name } =" )
55
+ assoc = object . embedded_relations [ attr_name ]
56
+ if assoc &.one? && Hash === value
57
+ object . send ( "#{ attr_name } =" , clone_with_hash ( assoc . klass , value ) )
58
+ elsif assoc &.many? && Array === value
59
+ docs = value . map { |h | clone_with_hash ( assoc . klass , h ) }
60
+ object . send ( "#{ attr_name } =" , docs )
61
+ elsif object . respond_to? ( "#{ attr_name } =" )
35
62
object . send ( "#{ attr_name } =" , value )
36
63
else
37
64
object . attributes [ attr_name ] = value
38
65
end
39
66
end
40
67
end
41
68
end
42
- alias :dup :clone
43
-
44
- private
45
69
46
70
# Clone the document attributes
47
71
#
0 commit comments