Search code examples
mongodbmongoose

Updating an array within an array gives Location40324 error in mongodb


I am creating a student management system. I am using nodejs, expressjs, ejs and mongoose. Here is the code for Student model:

const mongoose = require('mongoose')
const studentSchema = mongoose.Schema({
    name: {type: String, required: true},
    batchInfo: [{
        batch_id: {type: mongoose.Schema.Types.ObjectId, ref: "Batch", required: true},
        studentID: {type: String, required: true, unique: true},
        payments: [{type: Number, required: true}],
        active: {type: Boolean, required: true}
    }],
})
const Student = mongoose.model('Student', studentSchema, 'students')
module.exports = Student

In the batchInfo array, there should be different batch's info.

I have a student document like this:

{
    "_id": "6683591adb3c21133fd99e12",
    "name": "John",
    "batchInfo": [
    {
        "batch_id": "66834a6d4b221111587def7b",
        "studentID": "252105",
        "payments": [2000, 1000]
        "active": false
    },
    {
        "batch_id": "668353316c57406f16e14449",
        "studentID": "252301",
        "payments": [1000]
        "active": true,
    }
  ]
}

I filters this document based on batchInfo array having "active": true. I got this document:

{
    "_id": "6683591adb3c21133fd99e12",
    "name": "John",
    "batchInfo": [
    {
        "batch_id": "668353316c57406f16e14449",
        "studentID": "252301",
        "active": true,
        "payments": [1000]
    }
  ]
}

To achieve this document, I applied this express code:

const mongoose = require('mongoose')
const Student = require("./models/Student")
app.post('/update/:id', async function (req, res) {
    try {
        const student = await Student.aggregate([
            {"$match": {_id: new mongoose.Types.ObjectId(req.params.id)}},
            {"$project": {batchInfo: {"$filter": {input: "$batchInfo", as: "item", cond: {"$eq": ["$$item.active", true]}}}, name: 1}}
        ])
        res.send(student[0])
    } catch (error) {
        res.send(error)
    }
})

Now I want to insert an element to the payments array. So I tried this code:

const mongoose = require('mongoose')
const Student = require("./models/Student")
app.post('/update/:id', async function (req, res) {
    try {
        const student = await Student.aggregate([
            {"$match": {_id: new mongoose.Types.ObjectId(req.params.id)}},
            {"$project": {batchInfo: {"$filter": {input: "$batchInfo", as: "item", cond: {"$eq": ["$$item.active", true]}}}, name: 1}},
            {"$push": {"batchInfo.payments": 3000}}
        ])
        res.send(student[0])
    } catch (error) {
        res.send(error)
    }
})

After trying this code I got an error like this:

{
  "ok": 0,
  "code": 40324,
  "codeName": "Location40324"
}

How to solve this problem?


Solution

  • There are several different approaches to updating array elements by $pushing a new value into an array.

    If you know you are only finding one array element you can just use the $ positional operator to update the first matching array element in a findOneAndUpdate() method like so:

    const student = await Student.findOneAndUpdate({
      _id: new mongoose.Types.ObjectId(req.params.id),
      "batchInfo.active": true //< must match array element too
    },
    {
      $push: {
        "batchInfo.$.payments": 3000
      }
    }, {new: true});
    

    See HERE for a working example.

    Alternatively you can update several array elements by using arrayFilters option like so:

    const student = await Student.findOneAndUpdate({
      _id: new mongoose.Types.ObjectId(req.params.id)
    },
    {
      $push: {
        "batchInfo.$[element].payments": 3000
      }
    },
    {
      arrayFilters: [
        {
          "element.active": true
        }
      ]
    });
    

    See HERE for a working example.

    To match one element from the array based on multiple conditions you can use $elemMatch like so:

    const student = await Student.findOneAndUpdate({
      _id: new mongoose.Types.ObjectId(req.params.id),
      batchInfo: {
        $elemMatch: {
          active: true,
          batch_id: new mongoose.Types.ObjectId("668353316c57406f16e14420")
        }
      {
    },
    {
      $push: {
        "batchInfo.$.payments": 3000
      }
    }, {new: true});
    

    See HERE for a working example.