10

In a node js project, where I use mongoose, I'm trying to update an item of array inside a document.
I have a users schema which has a notifications array inside. Each user has an _id and each notifications item has an _id.

How can update a notification by, user id and notification id?

USER SCHEMA:

const schema = mongoose.Schema({
  notifications: [{
    id: { type: mongoose.Schema.ObjectId },
    title: { type: String },
    body: { type: String },
    isRead: { type: Boolean, default: false },
    date: { type: Date }
  }],
  token: { type: String },
  isActive: { type: Boolean, default: false }
}, { timestamps: {} }) 

This is what I've tried so far:

exports.updateNotification = (req, res) => {

  // Note: in req.params I receive id(for user id) and notificationId.

  UserModel.findByIdAndUpdate(req.params.id, { $set: { notifications: req.body } }, { safe: true, upsert: true, new: true })
    .then((result) => res.status(201).json({
      success: true,
      message: res.__('success.add'),
      user: result
    }))
    .catch(err => errorHandler.handle(err, res))
}

The method findByIdAndUpdate I've used it to updated the user, but I don't know how to adapt it for updating a notification, by id.

V. Sambor
  • 12,361
  • 6
  • 46
  • 65

2 Answers2

14

Try using $elemMatch,

UserModel.findOneAndUpdate({_id: 1, notifications: {$elemMatch: {id: 2}}},
                           {$set: {'notifications.$.title': req.body.title,
                                   'notifications.$.body': req.body.body,}}, // list fields you like to change
                           {'new': true, 'safe': true, 'upsert': true});
ViKiG
  • 764
  • 9
  • 21
  • When I do this way, I get : Mongo Error: The positional operator did not find the match needed from the query. Unexpanded update: notifications.$.title – V. Sambor Feb 12 '18 at 09:59
  • Does the element with the `id` you gave exist in your database? I simply gave 2 as a placeholder. It should be `mongoose.Types.ObjectId` type – ViKiG Feb 12 '18 at 10:02
  • Yes, just rechecked 3rd time :) – V. Sambor Feb 12 '18 at 10:04
  • Can you show me the object you are trying to query. – ViKiG Feb 12 '18 at 10:04
  • Yes. Here is the object: https://ibb.co/fLRmJ7 and here is the query: https://ibb.co/fO4dWS and here you have the code: https://ibb.co/g0F7Qn – V. Sambor Feb 12 '18 at 10:09
  • and I console log before the id's to recheck, they arrives good in the function... – V. Sambor Feb 12 '18 at 10:14
  • Hey, what is contained in `req.params.notificationId`, I suspect it is a `string`, Try changing it to `new mongoose.Types.ObjectId(req.params.notificationId)` – ViKiG Feb 12 '18 at 10:20
  • And also, the `notifications` objects have `_id` field not `id`. – ViKiG Feb 12 '18 at 10:21
  • the last comment solved the problem. `notifications._id`. By the way it works with both ways `elemMatch` or without. I choose the simplest. https://ibb.co/jzVGkn Thank you very much! – V. Sambor Feb 12 '18 at 10:29
  • 1
    The `dot` and `$elemMatch` are not the same. The `dot` notation will return any of the array entries satisfy any of the condition, more like `or`. `$elemMatch` will return only documents which satisfy all the conditions. In your case since its only a single condition, it worked out. Try multiple conditions, it will not always return right ones. Use `$elemMatch` – ViKiG Feb 12 '18 at 10:44
  • Thx. Do you know if there is something to update the provided fields? For the moment I've created a function to construct the $set object, but maybe there is something native, already... (https://ibb.co/fuQoy7) – V. Sambor Feb 12 '18 at 10:56
  • native to do something like your `bindFieldsToUpdate` function does. AFAIK, nothing like that. – ViKiG Feb 12 '18 at 11:01
  • Please can you explain that what is the `safe: true` and `upsert: true` for? – Tayyab Ferozi Dec 17 '20 at 17:44
  • safe will get us if any errors happened on mongodb to our callback, and upsert is for if document exists update it or if does not insert a new one. – ViKiG Dec 18 '20 at 07:39
  • Suppose there is 10 object in the array. Now I want to update the title field of id 4,5,6 at once. So, how can we do that because you are updating only one record? Can we update the record based on the array? – Shiv Yadav Dec 28 '22 at 05:44
  • I think the answer in this link gives you want you looking for: [answer](https://stackoverflow.com/questions/4669178/how-to-update-multiple-array-elements-in-mongodb#answer-46054172) – ViKiG Jan 01 '23 at 04:33
4
UserModel.update({'_id':1,'notifications.id': 2}, {'$set': {
    'notifications.$.title': 'updated title',
    'notifications.$.body': 'updated body'
}}, function(err) { ...}

Haven't tested but this should work.

Ridham Tarpara
  • 5,970
  • 4
  • 19
  • 39
  • Thank you, I'll check and let you know! – V. Sambor Feb 12 '18 at 08:43
  • It sais it is successfully updated, but there is no change in the db :-| I tried with `update` and `findOneAndUpdate` but none works... – V. Sambor Feb 12 '18 at 09:45
  • I tried this: UserModel.update( { _id: req.params.id, 'notifications.id': req.params.notificationId }, { $set: { 'notifications.$.title': 'baubau' } }, { new: true }) .then((result) => res.status(202).json({ success: true, message: res.__('success.update'), res: result })) .catch(err => errorHandler.handle(err, res)) – V. Sambor Feb 12 '18 at 09:46
  • Suppose there is 10 object in the array. Now I want to update the title field of id 4,5,6 at once. So, how can we do that because you are updating only one record? Can we update the record based on the array? – Shiv Yadav Dec 28 '22 at 05:44