2

I'm currently building a REST-Api with node.js express and can't figure out how to update / add elements to the scores Array.

Here is one document from my MongoDB collection (how it should look):

field

My mongoose model:

const challengesSchema = new mongoose.Schema({
    createdBy:{
        type:String,
        required: true
    },
    mapType:{
        type:String,
        required: true
    },
    time:{
        type:Number,
        required: true
    },
    numberOfMaps:{
        type:String,
        required: true
    },
    maps:{
        type:Array,
        required: true
    },
    pin:{
        type: String,
        required: true
    },
    takenBy:{
        type: Array,
        required: false
    }
})

Basically I receive an id, which I can use to do Challenges.findById({challenge._id}) . I figured out how to add an object to the takenBy Array, like so:

Challenges.findOneAndUpdate(
  { _id: challenge._id },
  {
    $push: {
      takenBy: user
    }
  }
);

How can I add an element (score, like "20") to the scores array in the array 'takenBy' ?

0

3 Answers 3

3

You can push score and calculate the new TotalScore in one go using filtered positional operator $ like this.

router.put("/challenges/:id/:scoreId", async (req, res) => {
  let score = req.body.score;

  try {
    let result = await Challenges.findByIdAndUpdate(
      req.params.id,
      {
        $push: { "takenBy.$[inner].scores": score },
        $inc: {
          "takenBy.$[inner].TotalScore": score
        }
      },
      {
        arrayFilters: [{ "inner._id": req.params.scoreId }],
        new: true
      }
    );

    if (!result) return res.status(404);

    res.send(result);
  } catch (err) {
    console.log(err);
    res.status(500).send("Something went wrong");
  }
});

Test:

Let's have this existing document:

{
    "_id" : ObjectId("5e08fe4c0bc1b932e8726a0f"),
    "maps" : [ ],
    "takenBy" : [
        {
            "_id" : "id1",
            "TotalScore" : NumberInt(100),
            "scores" : [
                NumberInt(20),
                NumberInt(60),
                NumberInt(20)
            ]
        },
        {
            "_id" : "id2",
            "TotalScore" : NumberInt(30),
            "scores" : [
                NumberInt(10),
                NumberInt(20)
            ]
        }
    ],
    "createdBy" : "5dfe0...",
    "mapType" : "World",
    "time" : NumberInt(2),
    "numberOfMaps" : "2",
    "pin" : "9558",
    "__v" : NumberInt(0)
}

If we want to add a score of 50 to then id1, we send a PUT request (http://..../challenges/5e08fe4c0bc1b932e8726a0f/id1) with this body:

{
    "score": 50
}

The result will be like this:

{
    "_id" : ObjectId("5e08fe4c0bc1b932e8726a0f"),
    "maps" : [ ],
    "takenBy" : [
        {
            "_id" : "id1",
            "TotalScore" : NumberInt(150),
            "scores" : [
                NumberInt(20),
                NumberInt(60),
                NumberInt(20),
                NumberInt(50)
            ]
        },
        {
            "_id" : "id2",
            "TotalScore" : NumberInt(30),
            "scores" : [
                NumberInt(10),
                NumberInt(20)
            ]
        }
    ],
    "createdBy" : "5dfe0...",
    "mapType" : "World",
    "time" : NumberInt(2),
    "numberOfMaps" : "2",
    "pin" : "9558",
    "__v" : NumberInt(0)
}

As you see the score is added to the related item array, and it's TotalScore is also incremented by 50 giving the TotalScore 150

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

9 Comments

Thanks! Awesome approach! Is there a way I could not just push to the scores array, but can specify the index where to put my new score in that array?
@user2557097 yes it seems possible, please have a look at the docs
thanks, it works! Like this: $push: { "takenBy.$[inner].scores": { $each: [score], $position: scorePosition }, },
@user2557097 great:) You did it by yourself. Please reconsider to accept this answer, this is the way how we update in mongoose.
I accepted your answer. Thanks again! Another problem I can't figure out: The premise of my question was that the takenBy array has objects with ids in it. When the user hasn't submitted a score, it does not add a score. Is there a way to create the object in the taken by array with the corresponding score array and TotalScore in one query? I could find the field and search for the Object with the id, if it exists, execute your query, if not create a new field. Is there a way to do this in one query?
|
1

You can retrieve the object first:

const updateChallenge = async (req,res) => {
    const challenge = await Challenges.findById(id);
    // Then you make duly changes to it with vanilla js:
    // Find which element in the array takenBy to update 
    // with regular js, methods like filter work or hardcode it

    challenge.takenBy[1].push(newElement);

    await challenge.save();
    // DONE! :)
}

Of course you can use destructuring if you prefer!

Comments

0

...how to update / add elements to the scores Array.

I would first retrieve that array... Push the new value, then update.

Something like:

Challenges.findById(challenge._id,(err, db_result) => {
  if(err){
    console.log(err)              // error management
    //...
  }

  if(db_result){

    // Get the takenBy array
    let takenBy = db_result.takenBy

    // Who are we talking about... You need the second _id for that record
    let who_id = user._id

    let targetIndex = null
    let byWho = takenBy.filter((who, index) => {

      let gotYa = who._id === who_id
      if (gotYa){
        targetIndex = index
      }
      return gotYa  // Boolean used as a return for .filter()
    })

    if(byWho.length>0){
      console.log("Got someone... Index: ", targetIndex)
    }else{
      console.log("No one found?")
      return
    }


    // Push the new value using the index where to update.
    takenBy[targetIndex].scores.push(challenge.score)

    Challenges.updateOne({_id:challenge._id},{
        $set: {
          takenBy: takenBy
        }
      }, (err, data) => {
        console.log(data)
      }
    )

  }
});

12 Comments

Ho, I missed the fact the score array is in the takenBy array... I will update my answer. But the concept is the same. It's get, push, set. ;)
hahem... Maybe you have to target the right takenBy element... Do you have the second id for it? Hold on a bit more... I'm editing
Try that... ;) We should be close.
It finally works! The problem in the end was that the updateOne function only changes the database document, when it has a callback function, like you did in the first line. I found this link. So everything works now, thanks!
Mind to accept the answer then? I'm happy for you. Enjoy coding!
|

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.