@@ -25,38 +25,70 @@ module InstanceMethods
25
25
# @since 3.0.0
26
26
def touch ( field = nil )
27
27
return false if _root . new_record?
28
- current = Time . now
29
- field = database_field_name ( field )
30
- write_attribute ( :updated_at , current ) if respond_to? ( "updated_at=" )
31
- write_attribute ( field , current ) if field
32
-
33
- # If the document being touched is embedded, touch its parents
34
- # all the way through the composition hierarchy to the root object,
35
- # because when an embedded document is changed the write is actually
36
- # performed by the composition root. See MONGOID-3468.
37
- if _parent
38
- # This will persist updated_at on this document as well as parents.
39
- # TODO support passing the field name to the parent's touch method;
40
- # I believe it should be read out of
41
- # _association.inverse_association.options but inverse_association
42
- # seems to not always/ever be set here. See MONGOID-5014.
43
- _parent . touch
44
- else
45
- # If the current document is not embedded, it is composition root
46
- # and we need to persist the write here.
47
- touches = touch_atomic_updates ( field )
48
- unless touches [ "$set" ] . blank?
49
- selector = atomic_selector
50
- _root . collection . find ( selector ) . update_one ( positionally ( selector , touches ) , session : _session )
51
- end
28
+
29
+ touches = __gather_touch_updates ( Time . now , field )
30
+ unless touches . blank?
31
+ selector = _root . atomic_selector
32
+ _root . collection . find ( selector ) . update_one ( positionally ( selector , '$set' => touches ) , session : _session )
52
33
end
53
34
54
- # Callbacks are invoked on the composition root first and on the
55
- # leaf-most embedded document last.
56
- # TODO add tests, see MONGOID-5015.
57
- run_callbacks ( :touch )
35
+ __run_touch_callbacks_from_root
58
36
true
59
37
end
38
+
39
+ # Recursively sets touchable fields on the current document and each of its
40
+ # parents, including the root node. Returns the combined atomic $set
41
+ # operations to be performed on the root document.
42
+ #
43
+ # @param [ Time ] now The timestamp used for synchronizing the touched time.
44
+ # @param [ Symbol ] field The name of an additional field to update.
45
+ #
46
+ # @return [ Hash<String, Time> ] The touch operations to perform as an atomic $set.
47
+ #
48
+ # @api private
49
+ def __gather_touch_updates ( now , field = nil )
50
+ field = database_field_name ( field )
51
+ write_attribute ( :updated_at , now ) if respond_to? ( "updated_at=" )
52
+ write_attribute ( field , now ) if field
53
+
54
+ touches = __extract_touches_from_atomic_sets ( field ) || { }
55
+ touches . merge! ( _parent . __gather_touch_updates ( now ) || { } ) if _parent
56
+ touches
57
+ end
58
+
59
+ # Recursively runs :touch callbacks for the document and its parents,
60
+ # beginning with the root document and cascading through each successive
61
+ # child document.
62
+ #
63
+ # @api private
64
+ #
65
+ # TODO add tests, see MONGOID-5015.
66
+ def __run_touch_callbacks_from_root
67
+ _parent . __run_touch_callbacks_from_root if _parent
68
+ run_callbacks ( :touch )
69
+ end
70
+
71
+ # Extract and remove the atomic updates for the touch operation(s)
72
+ # from the currently enqueued atomic $set operations.
73
+ #
74
+ # @api private
75
+ #
76
+ # @param [ Symbol ] field The optional field.
77
+ #
78
+ # @return [ Hash ] The field-value pairs to update atomically.
79
+ def __extract_touches_from_atomic_sets ( field = nil )
80
+ updates = atomic_updates [ '$set' ]
81
+ return { } unless updates
82
+
83
+ touchable_keys = %w( updated_at u_at )
84
+ touchable_keys << field . to_s if field . present?
85
+
86
+ updates . keys . each_with_object ( { } ) do |key , touches |
87
+ if touchable_keys . include? ( key . split ( '.' ) . last )
88
+ touches [ key ] = updates . delete ( key )
89
+ end
90
+ end
91
+ end
60
92
end
61
93
62
94
extend self
0 commit comments