1

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
    ]
}

1 Answer 1

1

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

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.