1

I have this document in my database:

{
    "_id": "ObjectId(...)",
    "chapters": [
        {
            "_id": "ObjectId(...)",
            "link": "128371.html",
            "content": ""
        }
    ]
}

The chapters array can have up to 3k items, and I have to populate each content attribute with some info. I want to be able to save the info I want inside the right object. Until now I was able to change the content attribute generally (in all items), but I am having trouble filtering it. This is what I managed to code using what I found in other questions:

let content = "Testing";
await models.ListNovel.updateOne(
  { link: novel_link },
  { $set: { "chapters.$[].content": content } }
);

I saw that { arrayFilters: [{ link: { $eq: chapter_link } }], multi: false } may work in some cases, but I don't use the link identifier in the update.

Thank you!

UPDATE

Similar to Suleyman's solution, I ended up with the following working code, I hope it may be useful for you.

await models.ListNovel.updateOne(
  { link: novel.link },
  { $set: { "chapters.$[elem].content": content } },
  {
    multi: true,
    arrayFilters: [{ "elem.link": { $eq: chapter.link } }]
  }
);

1 Answer 1

1

The condition in updateOne must match parent object, but you are using { link: novel_link } which belongs to the inner array object field, so it cannot find the document, and update doesn't happen.

To illustrate this, let's say your schema is like this:

const mongoose = require("mongoose");

const schema = new mongoose.Schema({
  name: String,
  chapters: [
    new mongoose.Schema({
      link: String,
      content: String
    })
  ]
});

module.exports = mongoose.model("ListNovel", schema);

Let's have this existing document in this collection:

{
    "_id": "5e498a1fe21eea0e10690e39",
    "name": "Novel1",
    "chapters": [
        {
            "_id": "5e498a1fe21eea0e10690e3b",
            "link": "128371.html",
            "content": ""
        },
        {
            "_id": "5e498a1fe21eea0e10690e3a",
            "link": "222222.html",
            "content": ""
        }
    ],
    "__v": 0
}

If we want to update this document's chapter with "link": "128371.html", first we need to find it with name or _id field, and update it using the filtered positional operator $.

router.put("/novels/:name", async (req, res) => {
  const novel_link = "128371.html";
  const content = "Testing";

  const result = await ListNovel.findOneAndUpdate(
    { name: req.params.name },
    {
      $set: { "chapters.$[chapter].content": content }
    },
    {
      arrayFilters: [{ "chapter.link": novel_link }],
      new: true
    }
  );
  res.send(result);
});

Here I used findOneAndUpdate to immediately retrieve the updated document, but you can also use the updateOne instead of findOneAndUpdate.

The result will be like this:

{
    "_id": "5e498a1fe21eea0e10690e39",
    "name": "Novel1",
    "chapters": [
        {
            "_id": "5e498a1fe21eea0e10690e3b",
            "link": "128371.html",
            "content": "Testing"  // => UPDATED
        },
        {
            "_id": "5e498a1fe21eea0e10690e3a",
            "link": "222222.html",
            "content": ""
        }
    ],
    "__v": 0
}
Sign up to request clarification or add additional context in comments.

1 Comment

Great explanation, Suleyman! After some time going through the documentation, I ended up with a pretty similar solution, using updateOne and the $eq operator.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.