我试图实施评级系统,而且我很难以合理的方式只允许每位用户评分一次。
简单地说,我的架构中有一系列评级,其中包含" rater"和等级,如:
var schema = new Schema({
//...
ratings: [{
by: {
type: Schema.Types.ObjectId
},
rating: {
type: Number,
min: 1,
max: 5,
validate: ratingValidator
}
}],
//...
});
var Model = mongoose.model('Model', schema);
当我收到请求时,如果用户尚未对此文档进行投票,我希望将用户评级添加到数组中,否则我希望更新评级(您不应该给出多个评级)< / p>
这样做的一种方法是找到文档,&#34;循环通过&#34;评级数组和搜索用户。如果用户已在阵列中获得评级,则更改评级,否则将推送新评级。就这样:
Model.findById(id)
.select('ratings')
.exec(function(err, doc) {
if(err) return next(err);
if(doc) {
var rated = false;
var ratings = doc.ratings;
for(var i = 0; i < ratings.length; i++) {
if(ratings[i].by === user.id) {
ratings[i].rating = rating;
rated = true;
break;
}
}
if(!rated) {
ratings.push({
by: user.id,
rating: rating
});
}
doc.markModified('ratings');
doc.save();
} else {
//Not found
}
});
有更简单的方法吗?让mongodb自动执行此操作的方法是什么?
mongodb $addToSet
运算符可能是另一种选择,但是我没有设法使用它,因为这可能允许来自同一用户的两个评分具有不同的分数。
答案 0 :(得分:0)
正如您所注意到的那样$addToSet
运算符在这种情况下不起作用,因为具有不同投票值的userId将是一个不同的值,并且它是该集合中唯一的成员。
因此,最好的方法是使用互补逻辑实际发出两个更新语句。实际上只会应用一个,具体取决于文档的状态:
async.series(
[
// Try to update a matching element
function(callback) {
Model.update(
{ "_id": id, "ratings.by": user.id },
{ "$set": { "ratings.$.rating": rating } },
callback
);
},
// Add the element where it does not exist
function(callback) {
Model.update(
{ "_id": id, "ratings.by": { "$ne": user.id } },
{ "$push": { "ratings": { "by": user.id, "rating": rating } }},
callback
);
}
],
function(err,result) {
// all done
}
);
原理很简单,尝试匹配文档的rating数组中的userId并更新条目。如果不满足该条件,则不更新任何文档。以同样的方式,尝试匹配rating数组中没有userId的文档,如果匹配则添加元素,否则将不会更新。
这绕过了mongoose的内置架构验证,因此您必须手动应用约束(或检查架构验证规则并手动应用),但在一个非常重要的方面,它优于您当前的方法。
当您.find()
文档并将其调用回客户端应用程序以使用代码进行修改时,则无法保证文档未在其他进程或请求的服务器上更改。因此,当您发出.save()
时,服务器上的文档可能不再处于读取时的状态,并且任何修改都可以覆盖在那里进行的更改。
因此,虽然服务器有两个操作而不是一个(并且您当前的代码仍然是两个操作),但是手动验证的两个邪恶中的较小者可能导致数据不一致。这两种更新方法将尊重可能同时发生在文档上的任何其他更新。