【问题标题】:Easy way to only allow one item per user in mongoose schema array猫鼬模式数组中每个用户只允许一个项目的简单方法
【发布时间】:2015-02-15 10:36:24
【问题描述】:

我正在尝试实施评分系统,但我正在努力以合理的方式只允许每个用户进行一次评分。

简单地说,我的架构中有一个评分数组,包含“评分者”和评分,如下所示:

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);

当我收到请求时,如果用户尚未对该文档进行投票,我希望将用户评分添加到数组中,否则我希望更新评分(您不能给出多个评分)

一种方法是找到文档,“循环”评级数组并搜索用户。如果用户已经在数组中获得了评分,则更改评分,否则推送新评分。因此:

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 运算符可能是一个替代方案,但是我没有设法将其用于此目的,因为这可能允许来自同一用户的两个具有不同分数的评分。

【问题讨论】:

    标签: node.js mongodb mongoose mongodb-query


    【解决方案1】:

    正如您所注意到的,$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() 时,服务器上的文档可能不再处于读取时的状态,并且任何修改都可能覆盖在那里所做的更改。

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

    【讨论】:

    • 谢谢,太好了。比我的提议要好得多。
    猜你喜欢
    • 2016-04-24
    • 1970-01-01
    • 2019-04-06
    • 2012-06-02
    • 2018-12-15
    • 2015-05-15
    • 1970-01-01
    • 2020-03-01
    • 1970-01-01
    相关资源
    最近更新 更多