/
traversable.rb
213 lines (196 loc) · 6.14 KB
/
traversable.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# encoding: utf-8
module Mongoid
# Provides behaviour around traversing the document graph.
#
# @since 4.0.0
module Traversable
extend ActiveSupport::Concern
def _parent
@__parent
end
def _parent=(p)
@__parent = p
end
# Get all child +Documents+ to this +Document+, going n levels deep if
# necessary. This is used when calling update persistence operations from
# the root document, where changes in the entire tree need to be
# determined. Note that persistence from the embedded documents will
# always be preferred, since they are optimized calls... This operation
# can get expensive in domains with large hierarchies.
#
# @example Get all the document's children.
# person._children
#
# @return [ Array<Document> ] All child documents in the hierarchy.
def _children
@__children ||= collect_children
end
# Collect all the children of this document.
#
# @example Collect all the children.
# document.collect_children
#
# @return [ Array<Document> ] The children.
#
# @since 2.4.0
def collect_children
children = []
embedded_relations.each_pair do |name, metadata|
without_autobuild do
child = send(name)
Array.wrap(child).each do |doc|
children.push(doc)
children.concat(doc._children)
end if child
end
end
children
end
# Marks all children as being persisted.
#
# @example Flag all the children.
# document.flag_children_persisted
#
# @return [ Array<Document> ] The flagged children.
#
# @since 3.0.7
def flag_children_persisted
_children.each do |child|
child.new_record = false
end
end
# Determines if the document is a subclass of another document.
#
# @example Check if the document is a subclass
# Square.new.hereditary?
#
# @return [ true, false ] True if hereditary, false if not.
def hereditary?
self.class.hereditary?
end
# Sets up a child/parent association. This is used for newly created
# objects so they can be properly added to the graph.
#
# @example Set the parent document.
# document.parentize(parent)
#
# @param [ Document ] document The parent document.
#
# @return [ Document ] The parent document.
def parentize(document)
self._parent = document
end
# Remove a child document from this parent. If an embeds one then set to
# nil, otherwise remove from the embeds many.
#
# This is called from the +RemoveEmbedded+ persistence command.
#
# @example Remove the child.
# document.remove_child(child)
#
# @param [ Document ] child The child (embedded) document to remove.
#
# @since 2.0.0.beta.1
def remove_child(child)
name = child.metadata_name
if child.embedded_one?
remove_ivar(name)
else
relation = send(name)
relation.send(:delete_one, child)
end
end
# After children are persisted we can call this to move all their changes
# and flag them as persisted in one call.
#
# @example Reset the children.
# document.reset_persisted_children
#
# @return [ Array<Document> ] The children.
#
# @since 2.1.0
def reset_persisted_children
_children.each do |child|
child.move_changes
child.new_record = false
end
_reset_memoized_children!
end
# Resets the memoized children on the object. Called internally when an
# embedded array changes size.
#
# @api semiprivate
#
# @example Reset the memoized children.
# document._reset_memoized_children!
#
# @return [ nil ] nil.
#
# @since 5.0.0
def _reset_memoized_children!
_parent._reset_memoized_children! if _parent
@__children = nil
end
# Return the root document in the object graph. If the current document
# is the root object in the graph it will return self.
#
# @example Get the root document in the hierarchy.
# document._root
#
# @return [ Document ] The root document in the hierarchy.
def _root
object = self
while (object._parent) do object = object._parent; end
object.with(@persistence_options) || self
end
# Is this document the root document of the hierarchy?
#
# @example Is the document the root?
# document._root?
#
# @return [ true, false ] If the document is the root.
#
# @since 3.1.0
def _root?
_parent ? false : true
end
module ClassMethods
# Determines if the document is a subclass of another document.
#
# @example Check if the document is a subclass.
# Square.hereditary?
#
# @return [ true, false ] True if hereditary, false if not.
def hereditary?
!!(Mongoid::Document > superclass)
end
# When inheriting, we want to copy the fields from the parent class and
# set the on the child to start, mimicking the behaviour of the old
# class_inheritable_accessor that was deprecated in Rails edge.
#
# @example Inherit from this class.
# Person.inherited(Doctor)
#
# @param [ Class ] subclass The inheriting class.
#
# @since 2.0.0.rc.6
def inherited(subclass)
super
@_type = nil
subclass.aliased_fields = aliased_fields.dup
subclass.localized_fields = localized_fields.dup
subclass.fields = fields.dup
subclass.pre_processed_defaults = pre_processed_defaults.dup
subclass.post_processed_defaults = post_processed_defaults.dup
subclass._declared_scopes = Hash.new { |hash,key| self._declared_scopes[key] }
# We only need the _type field if inheritance is in play, but need to
# add to the root class as well for backwards compatibility.
unless fields.has_key?("_type")
field(:_type, default: self.name, type: String)
end
subclass_default = subclass.name || ->{ self.class.name }
subclass.field(:_type, default: subclass_default, type: String, overwrite: true)
end
end
end
end