Matt
Matt

Reputation: 117

Update multiple objects in nested array

Question: Is it possible to update multiple objects in a nested array based on another field in the objects, using a single Mongoose method?

More specifically, I'm trying to update subscribed in each object of the Contact.groups array where the object's name value is included in groupNames. Solution 1 works, but it seems messy and inefficient to use both findOne() and save(). Solution 2 is close to working with just findOneAndUpdate(), but only the first eligible object in Contact.groups is updated. Am I able to update all the eligible objects using just findOneAndUpdate()?

Contact schema (trimmed down to relevant info):

{
    phone: { type: String, unique: true },
    groups: [
        {
            name: { type: String },
            subscribed: { type: Boolean }
        }
    ]
}

Variables I have at this point:

const phoneToUpdate = '1234567890'    // Contact.phone to find
const groupNames = [ 'A', 'B', 'C' ]  // Contacts.groups <obj>.name must be one of these
const subStatus = false               // Contacts.groups <obj>.subscribed new value

Solution 1 (seems inefficient and messy):

Contact
    .findOne({ phone: phoneToUpdate })
    .then(contact => {
        contact.groups
            .filter(g => groupNames.includes(g.name))
            .forEach(g => g.subscribed = subStatus)

        contact
            .save()
            .then(c => console.log(c))
            .catch(e => console.log(e))
    })
    .catch(e => console.log(e))

Solution 2 (only updates the first matching object):

Contact
    .findOneAndUpdate(
        { phone: phoneToUpdate, 'groups.name': { $in: groupNames } },
        { $set: { 'groups.$.subscribed': subStatus } },
        { new: true }
    )
    .then(c => console.log(c))
    .catch(error => console.log(error))

// Example Contact after the findOneAndUpdate
{
    phone: '1234567890',
    groups: [
        { name: 'A', subscribed: false },
        { name: 'B', subscribed: true }  // Should also be false
    ]
}

Upvotes: 1

Views: 916

Answers (1)

NeNaD
NeNaD

Reputation: 20304

You can not use $ operator since he will act as a placeholder only for the first match.

The positional $ operator acts as a placeholder for the first match of the update query document.

What you can use is arrayFilters operator. You can modify your query like this:

Contact.findOneAndUpdate({
  "phone": phoneToUpdate
},
{
  "$set": {
    "groups.$[elem].subscribed": subStatus 
  }
},
{
  "arrayFilters": [
    {
      "elem.name": {
        "$in": groupNames 
      }
    }
  ]
})

Here is a working example: https://mongoplayground.net/p/sBT-aC4zW93

Upvotes: 1

Related Questions