Skip to content

Commit 4fb3901

Browse files
Backport MONGOID-5352 to 7.5-stable (#5646)
* Backport MONGOID-5352 to 7.4-stable (#5286) * MONGOID-5352 assign_attributes is working incorrectly since 7.3.4 (#5274) * MONGOID-5352 add fix for this issue, other tests failing * MONGOID-5352 fix two tests, two to go * MONGOID-5352 fix all tests * MONGOID-5352 fix some tests * MONGOID-5352 don't set caches before setting association * MONGOID-5352 rework solution * Update lib/mongoid/association/embedded/batchable.rb * MONGOID-5352 answer comments * MONGOID-5352 update comment * Add relevant changes from MONGOID-4869 * fix test * remove test for remvoed code * Fix mongoid_spec.rb:55 expectation of renamed disconnect!/close in 'mongo' gem --------- Co-authored-by: Neil Shweky <[email protected]>
1 parent 61eda97 commit 4fb3901

File tree

5 files changed

+188
-4
lines changed

5 files changed

+188
-4
lines changed

lib/mongoid/association/embedded/batchable.rb

+20-3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def batch_remove(docs, method = :delete)
8080
def batch_replace(docs)
8181
if docs.blank?
8282
if _assigning? && !empty?
83-
_base.delayed_atomic_sets.clear
83+
_base.delayed_atomic_sets.delete(path)
84+
clear_atomic_path_cache
8485
_base.add_atomic_unset(first)
8586
target_duplicate = _target.dup
8687
pre_process_batch_remove(target_duplicate, :delete)
@@ -92,7 +93,8 @@ def batch_replace(docs)
9293
_base.delayed_atomic_sets.clear unless _assigning?
9394
docs = normalize_docs(docs).compact
9495
_target.clear and _unscoped.clear
95-
_base.delayed_atomic_unsets.clear
96+
_base.delayed_atomic_unsets.delete(path)
97+
clear_atomic_path_cache
9698
inserts = execute_batch_set(docs)
9799
add_atomic_sets(inserts)
98100
end
@@ -234,7 +236,22 @@ def normalize_docs(docs)
234236
#
235237
# @return [ String ] The atomic path.
236238
def path
237-
@path ||= _unscoped.first.atomic_path
239+
@path ||= if _unscoped.empty?
240+
Mongoid::Atomic::Paths::Embedded::Many.position_without_document(_base, _association)
241+
else
242+
_unscoped.first.atomic_path
243+
end
244+
end
245+
246+
# Clear the cache for path and atomic_paths. This method is used when
247+
# the path method is used, and the association has not been set on the
248+
# document yet, which can cause path and atomic_paths to be calculated
249+
# incorrectly later.
250+
#
251+
# @api private
252+
def clear_atomic_path_cache
253+
self.path = nil
254+
_base.instance_variable_set("@atomic_paths", nil)
238255
end
239256

240257
# Set the atomic path.

lib/mongoid/atomic/paths/embedded/many.rb

+19
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,25 @@ def position
3434
locator = document.new_record? ? "" : ".#{document._index}"
3535
"#{pos}#{"." unless pos.blank?}#{document._association.store_as}#{locator}"
3636
end
37+
38+
class << self
39+
40+
# Get the position of where the document would go for the given
41+
# association. The use case for this function is when trying to
42+
# persist an empty list for an embedded association. All of the
43+
# existing functions for getting the position to store a document
44+
# require passing in a document to store, which we don't have when
45+
# trying to store the empty list.
46+
#
47+
# @param [ Document ] parent The parent document to store in.
48+
# @param [ Association ] association The association.
49+
#
50+
# @return [ String ] The position string.
51+
def position_without_document(parent, association)
52+
pos = parent.atomic_position
53+
"#{pos}#{"." unless pos.blank?}#{association.store_as}"
54+
end
55+
end
3756
end
3857
end
3958
end

spec/mongoid/association/embedded/embeds_many/proxy_spec.rb

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "spec_helper"
4+
require_relative '../embeds_many_models.rb'
45

56
describe Mongoid::Association::Embedded::EmbedsMany::Proxy do
67

@@ -4649,4 +4650,24 @@ class DNS::Record
46494650
end
46504651
end
46514652
end
4653+
4654+
context "when using assign_attributes with an already populated array" do
4655+
let(:post) { EmmPost.create! }
4656+
4657+
before do
4658+
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'a'}],
4659+
user_tags: [{id: BSON::ObjectId.new, title: 'b'}])
4660+
post.save!
4661+
post.reload
4662+
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'c'}],
4663+
user_tags: [])
4664+
post.save!
4665+
post.reload
4666+
end
4667+
4668+
it "has the correct embedded documents" do
4669+
expect(post.company_tags.length).to eq(1)
4670+
expect(post.company_tags.first.title).to eq("c")
4671+
end
4672+
end
46524673
end

spec/mongoid/association/embedded/embeds_many_models.rb

+121
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,124 @@ class EmmOuter
6767

6868
field :level, :type => Integer
6969
end
70+
71+
class EmmCustomerAddress
72+
include Mongoid::Document
73+
74+
embedded_in :addressable, polymorphic: true, inverse_of: :work_address
75+
end
76+
77+
class EmmFriend
78+
include Mongoid::Document
79+
80+
embedded_in :befriendable, polymorphic: true
81+
end
82+
83+
class EmmCustomer
84+
include Mongoid::Document
85+
86+
embeds_one :home_address, class_name: 'EmmCustomerAddress', as: :addressable
87+
embeds_one :work_address, class_name: 'EmmCustomerAddress', as: :addressable
88+
89+
embeds_many :close_friends, class_name: 'EmmFriend', as: :befriendable
90+
embeds_many :acquaintances, class_name: 'EmmFriend', as: :befriendable
91+
end
92+
93+
class EmmUser
94+
include Mongoid::Document
95+
include Mongoid::Timestamps
96+
97+
embeds_many :orders, class_name: 'EmmOrder'
98+
end
99+
100+
class EmmOrder
101+
include Mongoid::Document
102+
103+
field :amount, type: Integer
104+
105+
embedded_in :user, class_name: 'EmmUser'
106+
end
107+
108+
module EmmSpec
109+
# There is also a top-level Car class defined.
110+
class Car
111+
include Mongoid::Document
112+
113+
embeds_many :doors
114+
end
115+
116+
class Door
117+
include Mongoid::Document
118+
119+
embedded_in :car
120+
end
121+
122+
class Tank
123+
include Mongoid::Document
124+
125+
embeds_many :guns
126+
embeds_many :emm_turrets
127+
# This association references a model that is not in our module,
128+
# and it does not define class_name hence Mongoid will not be able to
129+
# figure out the inverse for this association.
130+
embeds_many :emm_hatches
131+
132+
# class_name is intentionally unqualified, references a class in the
133+
# same module. Rails permits class_name to be unqualified like this.
134+
embeds_many :launchers, class_name: 'Launcher'
135+
end
136+
137+
class Gun
138+
include Mongoid::Document
139+
140+
embedded_in :tank
141+
end
142+
143+
class Launcher
144+
include Mongoid::Document
145+
146+
# class_name is intentionally unqualified.
147+
embedded_in :tank, class_name: 'Tank'
148+
end
149+
end
150+
151+
# This is intentionally on top level.
152+
class EmmTurret
153+
include Mongoid::Document
154+
155+
embedded_in :tank, class_name: 'EmmSpec::Tank'
156+
end
157+
158+
# This is intentionally on top level.
159+
class EmmHatch
160+
include Mongoid::Document
161+
162+
# No :class_name option on this association intentionally.
163+
embedded_in :tank
164+
end
165+
166+
class EmmPost
167+
include Mongoid::Document
168+
169+
embeds_many :company_tags, class_name: "EmmCompanyTag"
170+
embeds_many :user_tags, class_name: "EmmUserTag"
171+
end
172+
173+
174+
class EmmCompanyTag
175+
include Mongoid::Document
176+
177+
field :title, type: String
178+
179+
embedded_in :post, class_name: "EmmPost"
180+
end
181+
182+
183+
class EmmUserTag
184+
include Mongoid::Document
185+
186+
field :title, type: String
187+
188+
embedded_in :post, class_name: "EmmPost"
189+
end
190+

spec/mongoid_spec.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@
5151
end
5252

5353
it "disconnects from all active clients" do
54+
method_name = if Gem::Version.new(Mongo::VERSION) >= Gem::Version.new('2.19.0')
55+
:close
56+
else
57+
:disconnect!
58+
end
59+
5460
clients.each do |client|
55-
expect(client.cluster).to receive(:disconnect!).and_call_original
61+
expect(client.cluster).to receive(method_name).and_call_original
5662
end
5763
Mongoid.disconnect_clients
5864
end

0 commit comments

Comments
 (0)