1

I have the following model:

user :{ 
    overall_stats: {
            elo: {
                type: Array,
                default: {
                    date: new Date(),
                    ranking: 1000,
                }
            },
            nrGames: {
                ...
            },
            nrWins: {
                ...
            },
            winRate: {
                ...
            },
    },
   .
   .
   .
}

Now I want to push a new entry: { ranking: 1020, date: 2020-02-26T18:39:22.933Z }to the elo array. I tried different versions below but the elo property is completely gone after executing the function. nrGames and the others are updated correctly.

async function updateUserStatsAfterGameInDB(userId, newElo, numberOfGames, numberOfWins, winRate) {
    console.log(newElo);
    return await User.findOneAndUpdate(
        {
            _id: userId  // search query
        },
        {   
            overall_stats : {
                $push: {elo : newElo},
                nrGames : numberOfGames,
                nrWins : numberOfWins,
                winRate : winRate
            }
        },
        {
            new: true,                       // return updated doc
            runValidators: true,              // validate before update
            useFindAndModify: false
        }
    )
}

Where is the error?

7
  • this query also removes items from elo if previously present? Commented Feb 26, 2020 at 19:07
  • yes its not even an empty array, the whole elo property is gone in overall stats even though there is data from the default before Commented Feb 26, 2020 at 19:09
  • what is your MongoDB version ? You've wrong syntax.. Commented Feb 26, 2020 at 19:22
  • 1
    @Umar Hussain Thanks. also I need to put it as a string, then I get the correct result. $push: {'overall_stats.elo' : newElo} still need to combine it with the other stuff but thats optional for me, I can just call two methods. feel free to add it as an answer Commented Feb 26, 2020 at 19:32
  • 1
    Thanks. I have added my answer. I will suggest that you update the model in question to show that array is inside an object which is nested in your user object. It will give more context to future users looking at your question Commented Feb 26, 2020 at 19:43

2 Answers 2

1

The $push needs to be at top level in the update argument and your other update keys will be parallel to it. So this will be correct query:

User.findOneAndUpdate(
        {
            _id: userId  // search query
        },
        {   
            $push: {'overall_stats.elo' : newElo},
            overall_stats : {
                nrGames : numberOfGames,
                nrWins : numberOfWins,
                winRate : winRate
            }
        },
        {
            new: true,                       // return updated doc
            runValidators: true,              // validate before update
            useFindAndModify: false
        }
    )

Documentation for the $push: https://docs.mongodb.com/manual/reference/operator/update/push/

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

Comments

1

Your code has certain issues, try below code :

async function updateUserStatsAfterGameInDB(userId, newElo, numberOfGames, numberOfWins, winRate) {
    console.log(newElo);
    try {
        return await User.findOneAndUpdate(
            {
                _id: userId  // search query
            },
            {
                $push: { 'overall_stats.elo': newElo },
                'overall_stats.nrGames': numberOfGames,
                'overall_stats.nrWins': numberOfWins,
                'overall_stats.winRate': winRate
            },
            {
                new: true,                       // return updated doc
                runValidators: true,              // validate before update
                useFindAndModify: false
            }
        )
    } catch (error) {
        // Do something on error scenarios
    }
}

Note :

  1. In mongoose you don't need to explicitly use $set in update, as it will internally does it for you.

So your code below :

{
    overall_stats: {
        $push: { elo: newElo },
        nrGames: numberOfGames,
        nrWins : numberOfWins,
        winRate : winRate
    }
}

is getting converted as :

{
    $set: {
        overall_stats: {
            $push: { elo: newElo },
            nrGames: numberOfGames,
            nrWins : numberOfWins,
            winRate : winRate
        }
    }
}

So it is replacing overall_stats object with new object being passed in. Somehow it might not be throwing any error or ignoring $push: { elo: newElo }.

Similarly you can not use below one as well :

{   
            $push: {'overall_stats.elo' : newElo},
            overall_stats : {
                nrGames : numberOfGames,
                nrWins : numberOfWins,
                winRate : winRate
            }
  }

Cause you can't do $push & $set at the same-time on overall_stats object or its fields, as $push is pushing an element to elo field, meanwhile $set is completely replacing overall_stats object with new overall_stats object with only field passed in request, So kind of deleting elo field from overall_stats!! It would not work that way in .update() at least for .update operations which doesn't use aggregation pipeline, it will throw an error, So you need to specify each field that needs to be updated as like given above.

  1. It's not updating because $push is another operator like $set so it should not be inside $set.

  2. You need to wrap any async-await with try-catch for better error handling.

5 Comments

This makes it not delete the existing stuff but also does not insert into the DB. Also you are completely right about try catch of course
@solaire : Check my updated answer !! I've tested it - it's working as expected, Let me know if there are any issues !!
Works too. Will still go with the other solution for more modularity but thanks a lot for the explanation!
@solaire : not sure whether the accepted would work, cause I'm also in the same path as accepted answer at first & while on testing I've found it didn't work & noticed an issue, Hence updated my answer with explanation, if it does work for you then nice :-)
somehow it did (did not copy paste but adapted code according to answer)

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.