Skip to content

Commit 56ab615

Browse files
p-mongop
andauthored
Fix MONGOID-5136 .touch with custom field is broken for embedded documents (#5039)
Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent ee554b1 commit 56ab615

File tree

4 files changed

+56
-2
lines changed

4 files changed

+56
-2
lines changed

lib/mongoid/atomic.rb

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,35 @@ def touch_atomic_updates(field = nil)
344344
updates = atomic_updates
345345
return {} unless atomic_updates.key?("$set")
346346
touches = {}
347+
wanted_keys = %w(updated_at u_at)
348+
# TODO this permits field to be passed as an empty string in which case
349+
# it is ignored, get rid of this behavior.
350+
if field.present?
351+
wanted_keys << field.to_s
352+
end
347353
updates["$set"].each_pair do |key, value|
348-
key_regex = /updated_at|u_at#{"|" + field if field.present?}/
349-
touches.merge!({ key => value }) if key =~ key_regex
354+
if wanted_keys.include?(key.split('.').last)
355+
touches.update(key => value)
356+
end
350357
end
351358
{ "$set" => touches }
352359
end
360+
361+
# Returns the $set atomic updates affecting the specified field.
362+
#
363+
# @param [ String ] field The field name.
364+
#
365+
# @api private
366+
def set_field_atomic_updates(field)
367+
updates = atomic_updates
368+
return {} unless atomic_updates.key?("$set")
369+
sets = {}
370+
updates["$set"].each_pair do |key, value|
371+
if key.split('.').last == field
372+
sets.update(key => value)
373+
end
374+
end
375+
{ "$set" => sets }
376+
end
353377
end
354378
end

lib/mongoid/touchable.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ def touch(field = nil)
3838
# _association.inverse_association.options but inverse_association
3939
# seems to not always/ever be set here. See MONGOID-5014.
4040
_parent.touch
41+
42+
if field
43+
# If we are told to also touch a field, perform a separate write
44+
# for that field. See MONGOID-5136.
45+
# In theory we should combine the writes, which would require
46+
# passing the fields to be updated to the parents - MONGOID-5142.
47+
sets = set_field_atomic_updates(field)
48+
selector = atomic_selector
49+
_root.collection.find(selector).update_one(positionally(selector, sets), session: _session)
50+
end
4151
else
4252
# If the current document is not embedded, it is composition root
4353
# and we need to persist the write here.

spec/mongoid/touchable_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,24 @@
131131
include_examples 'updates the child'
132132
include_examples 'updates the parent when :touch is true'
133133
include_examples 'updates the parent when :touch is not set'
134+
135+
context 'when also updating an additional field' do
136+
it 'persists the update to the additional field' do
137+
entrance
138+
update_time
139+
entrance.touch(:last_used_at)
140+
141+
entrance.reload
142+
building.reload
143+
144+
# This is the assertion we want.
145+
entrance.last_used_at.should == update_time
146+
147+
# Check other timestamps for good measure.
148+
entrance.updated_at.should == update_time
149+
building.updated_at.should == update_time
150+
end
151+
end
134152
end
135153

136154
context "when the document is referenced" do

spec/mongoid/touchable_spec_models.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class Entrance
1515
include Mongoid::Timestamps
1616

1717
embedded_in :building
18+
19+
field :last_used_at, type: Time
1820
end
1921

2022
class Floor

0 commit comments

Comments
 (0)