Skip to content

Commit 1069e27

Browse files
committed
Merge branch 'master' into pr-5061
* master: MONGOID-5173 Specs should use bang (!) methods (without describe/context change) (mongodb#5109) Fix doc syntax RUBY-2783 Require bson 4-stable for Mongoid as bson master is 5.0 and not compatible with driver (mongodb#5113) MONGOID-5208 fix error on reloading nil embedded document (mongodb#5116) MONGOID-5206 fix bug where embedded document is not re-embedded (mongodb#5115) MONGOID-5207 Add Ruby 3.1 to GH Actions (mongodb#5114) MONGOID-5207 Use YAML.safe_load in specs (mongodb#5112) MONGOID-5199 Reloadable#reload_embedded_document doesn't respect session (mongodb#5105) Fix MONGOID-5198 Calling children_changed? on a deep cyclical data structure will cause semi-infinite looping (mongodb#5104)
2 parents 9b195c1 + 32eb8b7 commit 1069e27

File tree

19 files changed

+353
-55
lines changed

19 files changed

+353
-55
lines changed

.github/workflows/test.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@ jobs:
1818
fail-fast: false
1919
matrix:
2020
include:
21+
- mongodb: '5.0'
22+
ruby: ruby-3.1
23+
topology: server
24+
os: ubuntu-20.04
25+
task: test
26+
driver: current
27+
rails:
28+
i18n:
29+
gemfile: Gemfile
30+
experimental: false
31+
- mongodb: '5.0'
32+
ruby: ruby-3.1
33+
topology: replica_set
34+
os: ubuntu-20.04
35+
task: test
36+
driver: current
37+
rails:
38+
i18n:
39+
gemfile: Gemfile
40+
experimental: false
2141
- mongodb: '5.0'
2242
ruby: ruby-3.0
2343
topology: server
@@ -109,7 +129,7 @@ jobs:
109129
gemfile: Gemfile
110130
experimental: false
111131
- mongodb: '5.0'
112-
ruby: ruby-3.0
132+
ruby: ruby-3.1
113133
topology: replica_set
114134
os: ubuntu-20.04
115135
task: test

docs/reference/associations.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,7 @@ documents which satisfy the scope condition.. The scope may be either:
985985
associated model.
986986

987987
.. code-block:: ruby
988+
988989
class Trainer
989990
has_many :pets, scope: -> { where(species: 'dog') }
990991
has_many :toys, scope: :rubber

gemfiles/driver_master.gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
source "https://rubygems.org"
22

3-
gem 'bson', git: "https://github.com/mongodb/bson-ruby"
3+
gem 'bson', git: "https://github.com/mongodb/bson-ruby", branch: '4-stable'
44
gem 'mongo', git: "https://github.com/mongodb/mongo-ruby-driver"
55

66
gem 'actionpack'

lib/mongoid/association/embedded/embeds_many/proxy.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def build(attributes = {}, type = nil)
7272
doc.apply_post_processed_defaults
7373
yield(doc) if block_given?
7474
doc.run_callbacks(:build) { doc }
75-
_base._reset_memoized_children!
75+
_base._reset_memoized_descendants!
7676
doc
7777
end
7878

lib/mongoid/association/embedded/embeds_one/proxy.rb

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def initialize(base, target, association)
3131
characterize_one(_target)
3232
bind_one
3333
characterize_one(_target)
34-
_base._reset_memoized_children!
34+
_base._reset_memoized_descendants!
3535
_target.save if persistable?
3636
end
3737
end
@@ -53,6 +53,29 @@ def substitute(replacement)
5353
# The associated object will be replaced by the below update if non-nil, so only
5454
# run the callbacks and state-changing code by passing persist: false in that case.
5555
_target.destroy(persist: !replacement) if persistable?
56+
57+
# A little explanation on why this is needed... Say we have three assignments:
58+
#
59+
# canvas.palette = palette
60+
# canvas.palette = nil
61+
# canvas.palette = palette
62+
# Where canvas embeds_one palette.
63+
#
64+
# Previously, what was happening was, on the first assignment,
65+
# palette was considered a "new record" (new_record?=true) and
66+
# thus palette was being inserted into the database. However,
67+
# on the third assignment, we're trying to reassign the palette,
68+
# palette is no longer considered a new record, because it had
69+
# been inserted previously. This is not exactly accurate,
70+
# because the second assignment ultimately removed the palette
71+
# from the database, so it needs to be reinserted. Since the
72+
# palette's new_record is false, Mongoid ends up "updating" the
73+
# document, which doesn't reinsert it into the database.
74+
#
75+
# The change I introduce here, respecifies palette as a "new
76+
# record" when it gets removed from the database, so if it is
77+
# reassigned, it will be reinserted into the database.
78+
_target.new_record = true
5679
end
5780
unbind_one
5881
return nil unless replacement

lib/mongoid/atomic.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def atomic_updates(_use_indexes = false)
120120
process_flagged_destroys
121121
mods = Modifiers.new
122122
generate_atomic_updates(mods, self)
123-
_children.each do |child|
123+
_descendants.each do |child|
124124
child.process_flagged_destroys
125125
generate_atomic_updates(mods, child)
126126
end

lib/mongoid/changeable.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ def changed?
2828

2929
# Have any children (embedded documents) of this document changed?
3030
#
31-
# @example Have any children changed?
32-
# model.children_changed?
31+
# @note This intentionally only considers children and not descendants.
3332
#
3433
# @return [ true, false ] If any children have changed.
3534
def children_changed?
@@ -81,7 +80,7 @@ def move_changes
8180
# @example Handle post persistence.
8281
# document.post_persist
8382
def post_persist
84-
reset_persisted_children
83+
reset_persisted_descendants
8584
move_changes
8685
end
8786

lib/mongoid/persistable/creatable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def insert_as_root
8383
# @return [ true ] true.
8484
def post_process_insert
8585
self.new_record = false
86-
flag_children_persisted
86+
flag_descendants_persisted
8787
true
8888
end
8989

lib/mongoid/reloadable.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def reload
2222
end
2323

2424
reloaded = _reload
25-
if Mongoid.raise_not_found_error && reloaded.empty?
25+
if Mongoid.raise_not_found_error && (reloaded.nil? || reloaded.empty?)
2626
raise Errors::DocumentNotFound.new(self.class, _id, _id)
2727
end
2828
@attributes = reloaded
@@ -67,7 +67,7 @@ def reload_root_document
6767
# @return [ Hash ] The reloaded attributes.
6868
def reload_embedded_document
6969
extract_embedded_attributes({}.merge(
70-
collection(_root).find(_root.atomic_selector).read(mode: :primary).first
70+
collection(_root).find(_root.atomic_selector, session: _session).read(mode: :primary).first
7171
))
7272
end
7373

@@ -78,7 +78,8 @@ def reload_embedded_document
7878
#
7979
# @param [ Hash ] attributes The document in the db.
8080
#
81-
# @return [ Hash ] The document's extracted attributes.
81+
# @return [ Hash | nil ] The document's extracted attributes or nil if the
82+
# document doesn't exist.
8283
def extract_embedded_attributes(attributes)
8384
atomic_position.split(".").inject(attributes) do |attrs, part|
8485
attrs = attrs[part =~ /\d/ ? part.to_i : part]

lib/mongoid/traversable.rb

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -115,49 +115,82 @@ def self.get_discriminator_mapping(value)
115115
end
116116
end
117117

118-
# Get all child +Documents+ to this +Document+, going n levels deep if
119-
# necessary. This is used when calling update persistence operations from
118+
# Get all child +Documents+ to this +Document+
119+
#
120+
# @return [ Array<Document> ] All child documents in the hierarchy.
121+
#
122+
# @api private
123+
def _children
124+
@__children ||= collect_children
125+
end
126+
127+
# Get all descendant +Documents+ of this +Document+ recursively.
128+
# This is used when calling update persistence operations from
120129
# the root document, where changes in the entire tree need to be
121130
# determined. Note that persistence from the embedded documents will
122131
# always be preferred, since they are optimized calls... This operation
123132
# can get expensive in domains with large hierarchies.
124133
#
125-
# @example Get all the document's children.
126-
# person._children
134+
# @return [ Array<Document> ] All descendant documents in the hierarchy.
127135
#
128-
# @return [ Array<Document> ] All child documents in the hierarchy.
129-
def _children
130-
@__children ||= collect_children
136+
# @api private
137+
def _descendants
138+
@__descendants ||= collect_descendants
131139
end
132140

133141
# Collect all the children of this document.
134142
#
135-
# @example Collect all the children.
136-
# document.collect_children
137-
#
138143
# @return [ Array<Document> ] The children.
144+
#
145+
# @api private
139146
def collect_children
140147
children = []
141148
embedded_relations.each_pair do |name, association|
142149
without_autobuild do
143150
child = send(name)
144-
Array.wrap(child).each do |doc|
145-
children.push(doc)
146-
children.concat(doc._children)
147-
end if child
151+
if child
152+
children += Array.wrap(child)
153+
end
148154
end
149155
end
150156
children
151157
end
152158

153-
# Marks all children as being persisted.
159+
# Collect all the descendants of this document.
154160
#
155-
# @example Flag all the children.
156-
# document.flag_children_persisted
161+
# @return [ Array<Document> ] The descendants.
162+
#
163+
# @api private
164+
def collect_descendants
165+
children = []
166+
to_expand = []
167+
expanded = {}
168+
embedded_relations.each_pair do |name, association|
169+
without_autobuild do
170+
child = send(name)
171+
if child
172+
to_expand += Array.wrap(child)
173+
end
174+
end
175+
end
176+
until to_expand.empty?
177+
expanding = to_expand
178+
to_expand = []
179+
expanding.each do |child|
180+
next if expanded[child]
181+
expanded[child] = true
182+
children << child
183+
to_expand += child._children
184+
end
185+
end
186+
children
187+
end
188+
189+
# Marks all descendants as being persisted.
157190
#
158-
# @return [ Array<Document> ] The flagged children.
159-
def flag_children_persisted
160-
_children.each do |child|
191+
# @return [ Array<Document> ] The flagged descendants.
192+
def flag_descendants_persisted
193+
_descendants.each do |child|
161194
child.new_record = false
162195
end
163196
end
@@ -204,33 +237,28 @@ def remove_child(child)
204237
end
205238
end
206239

207-
# After children are persisted we can call this to move all their changes
208-
# and flag them as persisted in one call.
240+
# After descendants are persisted we can call this to move all their
241+
# changes and flag them as persisted in one call.
209242
#
210-
# @example Reset the children.
211-
# document.reset_persisted_children
212-
#
213-
# @return [ Array<Document> ] The children.
214-
def reset_persisted_children
215-
_children.each do |child|
243+
# @return [ Array<Document> ] The descendants.
244+
def reset_persisted_descendants
245+
_descendants.each do |child|
216246
child.move_changes
217247
child.new_record = false
218248
end
219-
_reset_memoized_children!
249+
_reset_memoized_descendants!
220250
end
221251

222-
# Resets the memoized children on the object. Called internally when an
252+
# Resets the memoized descendants on the object. Called internally when an
223253
# embedded array changes size.
224254
#
225-
# @api semiprivate
226-
#
227-
# @example Reset the memoized children.
228-
# document._reset_memoized_children!
229-
#
230255
# @return [ nil ] nil.
231-
def _reset_memoized_children!
232-
_parent._reset_memoized_children! if _parent
256+
#
257+
# @api private
258+
def _reset_memoized_descendants!
259+
_parent._reset_memoized_descendants! if _parent
233260
@__children = nil
261+
@__descendants = nil
234262
end
235263

236264
# Return the root document in the object graph. If the current document

0 commit comments

Comments
 (0)