Another way of doing this fully atomically is:
db.posts.update({'_id':post_id}, {
'$pull': {'votes.user':ObjectId},
'$addToSet': {votes: {
user: objectId,
date: Date,
isdownvote: isdownvote
}}
})
As already said, there is no upsert
for subdocuments so this kind of cheats and deletes the old vote and writes it again. The date
field should still be ok because if the user votes again then the date
field should change to the last time that user voted so in my mind that part makes sense.