Skip to content

Commit 402c502

Browse files
authored
MONGOID-4520 Set inverse keys on HABTM relationship when subsequently persisting document (#5295)
* MONGOID-4520 Set inverse keys on HABTM relationship when subsequently persisting document * Update spec/mongoid/association/syncable_spec.rb * Update spec/mongoid/association/syncable_spec.rb
1 parent e25370e commit 402c502

File tree

2 files changed

+58
-14
lines changed

2 files changed

+58
-14
lines changed

lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,33 @@ def <<(*args)
2929
return concat(docs) if docs.size > 1
3030
if doc = docs.first
3131
append(doc) do
32-
_base.add_to_set(foreign_key => doc.public_send(_association.primary_key))
33-
if child_persistable?(doc)
34-
doc.save
32+
# We ignore the changes to the value for the foreign key in the
33+
# changed_attributes hash in this block of code for two reasons:
34+
#
35+
# 1) The add_to_set method deletes the value for the foreign
36+
# key in the changed_attributes hash, but if we enter this
37+
# method with a value for the foreign key in the
38+
# changed_attributes hash, then we want it to exist outside
39+
# this method as well. It's used later on in the Syncable
40+
# module to set the inverse foreign keys.
41+
# 2) The reset_unloaded method accesses the value for the foreign
42+
# key on _base, which causes it to get added to the
43+
# changed_attributes hash. This happens because when reading
44+
# a "resizable" attribute, it is automatically added to the
45+
# changed_attributes hash. This is true only for the foreign
46+
# key value for HABTM associations as the other associations
47+
# use strings for their foreign key values. For consistency
48+
# with the other associations, we ignore this addition to
49+
# the changed_attributes hash.
50+
# See MONGOID-4843 for a longer discussion about this.
51+
reset_foreign_key_changes do
52+
_base.add_to_set(foreign_key => doc.public_send(_association.primary_key))
53+
54+
if child_persistable?(doc)
55+
doc.save
56+
end
57+
reset_unloaded
3558
end
36-
reset_unloaded
37-
clear_foreign_key_changes
3859
end
3960
end
4061
unsynced(_base, foreign_key) and self
@@ -202,21 +223,30 @@ def unscoped
202223

203224
# Clears the foreign key from the changed_attributes hash.
204225
#
205-
# reset_unloaded accesses the value for the foreign key on
206-
# _base, which causes it to get added to the changed_attributes
207-
# hash. This happens because when reading a "resizable" attribute
208-
# it is automatically added to the changed_attributes hash.
209-
# this is true only for the foreign key value for HABTM associations
210-
# as the other associations use strings for their foreign key values.
211-
# For consistency with the other associations, we clear the foreign
212-
# key from the changed_attributes hash.
213-
# See MONGOID-4843 for a longer discussion about this.
226+
# This is, in general, used to clear the foreign key from the
227+
# changed_attributes hash for consistency with the other referenced
228+
# associations.
214229
#
215230
# @api private
216231
def clear_foreign_key_changes
217232
_base.changed_attributes.delete(foreign_key)
218233
end
219234

235+
# Reset the value in the changed_attributes hash for the foreign key
236+
# to its value before executing the given block.
237+
#
238+
# @api private
239+
def reset_foreign_key_changes
240+
if _base.changed_attributes.key?(foreign_key)
241+
fk = _base.changed_attributes[foreign_key].dup
242+
yield if block_given?
243+
_base.changed_attributes[foreign_key] = fk
244+
else
245+
yield if block_given?
246+
clear_foreign_key_changes
247+
end
248+
end
249+
220250
# Appends the document to the target array, updating the index on the
221251
# document at the same time.
222252
#

spec/mongoid/association/syncable_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,18 @@ class TestModel
496496
expect(dog.reload.breed_ids).to eq([ breed.id ])
497497
end
498498
end
499+
500+
context "when setting one document on unpersisted HABTM association" do
501+
let!(:dog) { Dog.create! }
502+
let!(:breed) { Breed.new }
503+
504+
before do
505+
breed.dogs = Dog.all
506+
breed.save!
507+
end
508+
509+
it "sets the foreign key on the other side" do
510+
expect(dog.reload.breed_ids).to eq([breed._id])
511+
end
512+
end
499513
end

0 commit comments

Comments
 (0)