简体   繁体   中英

Add object to array of objects if object doesn't exist, otherwise update object

Desired Behaviour

Add object to array of objects if it doesn't exist, otherwise update object.

Each user can only add one rating.

Schema

"ratings" : [ 
    {
        "user_email" : "john@smith.com",
        "criteria_a" : 0,
        "criteria_b" : 3,
        "criteria_c" : 1,
        "criteria_d" : 5,
        "criteria_e" : 2
    },
    {
        "user_email" : "bob@smith.com",
        "criteria_a" : 1,
        "criteria_b" : 3,
        "criteria_c" : 5,
        "criteria_d" : 2,
        "criteria_e" : 1
    },
    {
        "user_email" : "jane@smith.com",
        "criteria_a" : 5,
        "criteria_b" : 3,
        "criteria_c" : 1,
        "criteria_d" : 0,
        "criteria_e" : 1
    }
]

What I've Tried

I've read this:

The $addToSet operator adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.

Source: https://docs.mongodb.com/manual/reference/operator/update/addToSet/

But I'm a bit confused because I think I am trying to:

  • $addToSet if object doesn't exist
  • $set (ie update the existing object) if it does exist

The following keeps adding rating objects to the array, even if a user has already added a rating.

var collection = mongo_client.db("houses").collection("houses");

var o_id = new ObjectID(id);

var query = { _id: o_id, "ratings.user_email": user_email };

var update = { $addToSet: { ratings: rating } };

var options = { upsert: true };

collection.updateOne(query, update, options, function(err, doc) {
    if (err) {
        res.send(err);
    } else {
        res.json({ doc: doc })
    }
});

Edit:

The following works if a user has already submitted a rating, but otherwise it throws the error The positional operator did not find the match needed from the query. :

var update = { $set: { "ratings.$": rating } };

You will need to perform this with two distinct actions:

  1. Attempt a $push if no rating for the given user exists yet, ie negate the user_email in your match portion of the update query as you're already doing.
  2. Perform a second update once again matching on "ratings.user_email": user_email , but this time perform a $set on "ratings.$": rating .

If no rating for the user exists yet, then (1) will insert it, otherwise no operation occurs. Now that the rating is guaranteed to exist, (2) will ensure that the rating is updated. In the case that (1) inserted a new rating, (2) will simply result in no operation occuring.

The result should look something like this:

collection.updateOne(
    { _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } },
    { $push: { ratings: rating } }
);

collection.updateOne(
    { _id: o_id, "ratings.user_email": user_email },
    { $set: { "ratings.$": rating } }
);

A more verbose example with comments is below:

// BEGIN new rating query/update variables
var query_new_rating = { _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } };

var update_new_rating = { $push: { ratings: rating } };
// END new rating  query/update variables


//  part 01 - attempt to push a new rating
collection.updateOne(query_new_rating, update_new_rating, function(error, result) {

    if (error) {
        res.send(error);
    } else {

        // get the number of modified documents
        // if 0 - the rating already exists
        // if 1 - a new rating was added
        var number_modified = result.result.nModified;

        // BEGIN if updating an existing rating
        if (number_modified === 0) {

            console.log("updating existing rating");

            // BEGIN existing rating query/update variables
            var query_existing_rating = { _id: o_id, "ratings.user_email": user_email };

            var update_existing_rating = { $set: { "ratings.$": rating } };
            // END existing rating query/update variables

            //  part 02 - update an existing rating
            collection.updateOne(query_existing_rating, update_existing_rating, function(error, result) {

                if (error) {
                    res.send(error);
                } else {

                    console.log("updated existing rating");
                    res.json({ result: result.result })
                }

            });

        }
        // END if updating an existing rating

        // BEGIN if adding a new rating
        else if (number_modified === 1) {

            console.log("added new rating");
            res.json({ result: result.result })

        }
        // END if adding a new rating

    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM