在mongoose模式数组中只允许每个用户使用一个项目的简单方法

时间:2014-12-16 23:53:07

标签: node.js mongodb mongoose mongodb-query

我试图实施评级系统,而且我很难以合理的方式只允许每位用户评分一次。

简单地说,我的架构中有一系列评级,其中包含" 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运算符可能是另一种选择,但是我没有设法使用它,因为这可能允许来自同一用户的两个评分具有不同的分数。

1 个答案:

答案 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()时,服务器上的文档可能不再处于读取时的状态,并且任何修改都可以覆盖在那里进行的更改。

因此,虽然服务器有两个操作而不是一个(并且您当前的代码仍然是两个操作),但是手动验证的两个邪恶中的较小者可能导致数据不一致。这两种更新方法将尊重可能同时发生在文档上的任何其他更新。