Question

I have a user, and inside I keep a list of items he has rated.

> db.users.findOne()
{
    "_id" : ObjectId("5321985944aebf2ca6f2075f"),
    "ratings" : [
        {
            "532192d344aebf2ca6f2075e" : 0
        },
        {
            "532199d144aebf2ca6f20760" : 1
        }
    ],
    "email" : "joe@example.com",
    "name" : "Joe Bloggs"
}

The ratings are in the form:

{ item_id : <score> }

The problem is, if a user rates the same item twice (with a different rating), instead of replacing the old one, it will add the new element to the array.

I am using $addToSet to avoid duplicates, but I would like to replace an old rating.

I'm currently doing this in a convoluted way, is there anything simpler? (I'm using Java but it doesn't matter if the solution doesn't.)

Was it helpful?

Solution

That is probably not the best schema design for a number of reasons. You would be better served by this:

{
    "_id" : ObjectId("5321985944aebf2ca6f2075f"),
    "ratings" : [
        {
            id: "532192d344aebf2ca6f2075e", score: 0
        },
        {
            id: "532199d144aebf2ca6f20760", score: 1
        }
    ],
    "email" : "joe@example.com",
    "name" : "Joe Bloggs"
}

At least in that way you can match on the id of the element of the array.

To your problem though. $addToSet is a nice convenience, but of course does not solve your issue. It still can be used, but if you want to add to a "score" like this then you would have to use (with the new model of course):

var count = db.collection.find({
  "_id" : ObjectId("5321985944aebf2ca6f2075f"),
  "ratings.id": "532192d344aebf2ca6f2075e" 
}).count();

if ( count != 0 ) {
   db.collection.update(
       {
           "_id" : ObjectId("5321985944aebf2ca6f2075f"),
           "ratings.id": "532192d344aebf2ca6f2075e" 
       },
       {
           "$inc": { "ratings.$.score": 1 }
       }
   );
} else {
    db.collection.update(
       {
           "_id" : ObjectId("5321985944aebf2ca6f2075f"),
           "ratings.id": "532192d344aebf2ca6f2075e" 
       },
       {
           "$push": {
               "id": newId,
               "score": newScore
           }
       }
    );
}

Or whatever equivalent in your implementation language.

So $addToSet becomes a bit redundant in your case, as you need to check to decide whether you $set or $inc as your case may be. More involved, but it's the only way to do it.

OTHER TIPS

Shouldn't you be using $set?

db.users.update({"email":"joe@example.com"}, {$set:{"ratings.item_id":5}})

This way, if no earlier rating was present, a new one will be added. If it's already present, it will be updated.

Note: You'll need to replace "item_id" with the actual item id from your client.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top