Vasily Pankratov
Vasily Pankratov

Reputation: 357

$ projection in mongoDB findOneAndUpdate()

I'm trying to build a simple task queue with express and mongoose. The idea is acquire a single client and return campaign id and client id (which is a subdocument of campaign). Each time someone acquires a client, its status code is set to 1. I've come up with the following query:

router.post('/lease', (err, res) => {
    Campaign.findOneAndUpdate({'isEnabled': true,  'clients.contact_status_code': 0}, {
            '$set': { 'clients.$.contact_status_code': 1 },
        },
        {
            new: true,
            projection: {
                'clients.$': true,
            },
        },
        (err, campaign) => {
            if (err) {
                return res.send(err);
            }

            res.json(campaign);
        }
    );
});

But all i'm getting after connecting to this endpoint is this:

{"_id":"591483241a84946a79626aef","clients":[{},{}]}

It seems to me that the problem is with the $ projection, but I have no idea how to fix this.

EDIT: I tried using the following code, utilizing $elemMatch:

router.post('/lease', (err, res) => {
    Campaign.findOneAndUpdate({'isEnabled': true,  'clients.contact_status_code': 0}, {
            '$set': { 'clients.$.contact_status_code': 1 },
        },
        {
            new: true,
            projection: {
                clients: {
                    '$elemMatch': {contact_status_code: 1},
                }
            },
        },
        (err, campaign) => {
            if (err) {
                return res.send(err);
            }

            res.json(campaign);
        }
    );
});

Unfortunately, each request yields the first subdocument in the collection, that matched the criteria -- not specifically the one that was updated. Here is an example:

Say, i have the following document in mongo:

    {
    "_id" : ObjectId("591493d95d48e2738b0d4317"),
    "name" : "asd",
    "template" : "{{displayname}}",
    "isEnabled" : true,
    "clients" : [
            {
                    "displayname" : "test",
                    "_id" : ObjectId("591493d95d48e2738b0d4319"),
                    "contact_status_code" : 0
            },
            {
                    "displayname" : "client",
                    "_id" : ObjectId("591493d95d48e2738b0d4318"),
                    "contact_status_code" : 0
            }
    ],
    "__v" : 0

}

I run the query for the first time and get the following result:

{"_id":"591493d95d48e2738b0d4317","clients":[{"displayname":"test","_id":"591493d95d48e2738b0d4319","contact_status_code":1}]}

Notice client id "591493d95d48e2738b0d4319" -- this time it runs as expected. But when i run the same query the second time, I get absolutely the same object, although I expect to get one with id "591493d95d48e2738b0d4318".

Upvotes: 3

Views: 9772

Answers (3)

AbdelghanyMh
AbdelghanyMh

Reputation: 419

Update for node MongoDB 3.6 driver

  • unlike findAndModify the function findOneAndUpdate doesn't have the option new in its options list
  • But you can use instead returnDocument
  • returnDocument accept before OR after
    • When set to after,returns the updated document rather than the original
    • When set to before,returns the original document rather than the updated.

The default is before.

returnOriginal is Deprecated Use returnDocument instead

  • Here is a working example:
const filter={'isEnabled': true,  'clients.contact_status_code': 0}
    const update={'$set': { 'clients.$.contact_status_code': 1 }}
    const options={
                returnDocument: 'after' //returns the updated document
                projection: {
                    clients: {
                        '$elemMatch': {contact_status_code: 0}, 
                        // 0 because the old record gets matched
                    },
                },
            }
    Campaign.findOneAndUpdate(filter,update, options
            ,(err, campaign) => {
                if (err) {
                    return res.send(err);
                }
                res.json(campaign);
            }
        );


findOneAndUpdate Node.js MongoDB Driver API 3.6 documentation

Upvotes: 0

Vasily Pankratov
Vasily Pankratov

Reputation: 357

The issue was with new: true

Here is a working example:

Campaign.findOneAndUpdate({'isEnabled': true,  'clients.contact_status_code': 0}, {
            '$set': { 'clients.$.contact_status_code': 1 },
        },
        {
            //new: true <-- this was causing the trouble
            projection: {
                clients: {
                    '$elemMatch': {contact_status_code: 0}, // 0 because the old record gets matched
                },
            },
        },
        (err, campaign) => {
            if (err) {
                return res.send(err);
            }

            res.json(campaign);
        }
    );

I assume, when the new:true is set, mongo loses the matching context. This approach returns the old record, unfortunately, but that still serves my needs to get the _id.

Upvotes: 9

Maciej Rostański
Maciej Rostański

Reputation: 405

Seems to me you are getting the campaign id, but you also want clients.$, have you tried clients.$._id?

Upvotes: 0

Related Questions