Here is an example of my collection "messages".
{[
_id: xxx,
shipment: {
_id: xxx
},
messages: [
{
_id: 123,
origin: 'driver'
isRead: false,
...
},
{
_id: 234,
origin: 'driver'
isRead: false,
...
},
{
_id: 345,
origin: 'driver'
isRead: true,
...
},
{
_id: 456,
origin: 'dispatch'
isRead: false,
...
},
]
]}
I'm updating the array objects using the following code which works great, however this returns the entire document back with all array objects, and I need to return a list of only the objects that were updated so I can pull a list of their IDs.
const message = await MessageModel.findOneAndUpdate(
{
_id: messageId
},
{
$set: {
'messages.$[elem].isRead': true
}
},
{
arrayFilters: [{
'elem.isRead': false,
'elem.origin': 'driver'
}],
new: true
}
);
Output I need in some form so I can grab the IDs that were updated to process further:
messages: [
{
_id: 123,
origin: 'driver'
isRead: true,
...
},
{
_id: 234,
origin: 'driver'
isRead: true,
...
}
]
const ids = [123,234];
I understand it's returning the entire document, but I can't figure out the best route to grab only what's updated without doing multiple queries and comparing.
I tried working with aggregate but am new to that and can't figure it out.
Any help greatly appreciated on the best route.
MongoDB updates work on documents. findOneAndUpdate
returns original document (whole document) or an updated document (whole document) based on the projection and option flag - So you cannot get only the updated messages
fields.
But if your goal is to avoid multiple db queries and yet be able to find out updated fields, then you can do following -
new: true
messages
fields to extract updated fieldsSample code -
const message = await MessageModel.findOneAndUpdate(
{
_id: messageId,
// Note below conditions are same as filters below
'messages.isRead': false,
'messages.origin': 'driver'
},
{
$set: {
'messages.$[elem].isRead': true
}
},
{
arrayFilters: [{
'elem.isRead': false,
'elem.origin': 'driver'
}]
}
);
// message will be the original document before update
const updatedIds = []
for (let i=0; i<message?.messages?.length; i++) {
if (message.messages[i].isRead === false && message.messages[i].origin === 'driver') {
// This means this field got update
updatedIds.push(message.messages[i]._id);
}
}
// return or use updatedIds
Reason for including isRead
& origin
in where clause -
As we want to update the document based on these flags only and we are interested in the doc only if an update happens, it makes sense to include respective filters in the where clause. This way, the query goes to the next step (update) only if at least one element of the messages
array is meeting the criteria.