【问题标题】:mongoose unique:true pre-save hook calls hook before validationmongoose unique:true pre-save hook 在验证前调用 hook
【发布时间】:2014-03-22 07:05:27
【问题描述】:

据我了解,mongoose 预保存钩子会在文档插入集合之前但在验证发生之后触发。因此,如果一次验证失败,则不会调用预保存挂钩。

在我的例子中,不管怎样,它们都会被调用:

下面的简单代码所做的是尝试创建一个模式,在该模式中,用户在注册时通过 _id 引用其他用户。添加了一个预保存挂钩以自动将新用户的 ID 推送到他们的推荐列表中。

所以用户 a 注册没有推荐 -> 好的

用户 b 向 a 注册作为推荐人 -> OK

用户 b2 使用与 b 相同的电子邮件注册(不正确,是唯一的)并引用 a -> 应该失败并且它不应该在 a.references 中推送 b2 的 ID

架构:

var userSchema = new Schema({
  email: {type:String, unique:true, required:true},
  isVerified: {type:Boolean, default:false},
  referredBy: {type:Schema.ObjectId, ref:'User'},
  referred: [{type:Schema.ObjectId, ref:'User'}],
});

userSchema.pre('save', function (next) {
  if (!this.isNew) return next();
  if (!this.referredBy) return next();

  User.findById(this.referredBy, function (err, doc) {
    if (err) return next(err);
    if (!doc) return next(new DbError(['referredBy not found: %s', this.referredBy]));
    doc.referred.push(this._id);
    doc.save(next);
  }.bind(this));
});

userSchema.path('referredBy').validate(function (value, respond) {
  User.findById(value, function (err, user) {
    if (err) throw err;
    if (!user) return respond(false);
    respond(true);
  });
}, 'doesntExit');

var User = mongoose.model('User', userSchema);

测试代码:

var a = new User();
a.email = 'a';

a.save(function () {
    var b = new User();
    b.email = 'b';
    b.referredBy = a._id;

    b.save(function () {
        var b2 = new User();
        b2.email = 'b';
        b2.referredBy = a._id;

        b2.save(function (err, doc) {
            console.log('error:', err); // duplicate error is thrown, which is OK
            console.log(!!doc);                 // this is false, which is OK
            User.findById(a._id, function (err, result) {
                console.log('# of referrals: ', result.referred.length); // 2, which is BAD
            });
        });
    });
});

其他所有检查,错误被抛出,失败发生,但所有预保存挂钩都被保存

知道如何解决这个问题,或者验证钩子后是否有真正的预保存?

【问题讨论】:

    标签: node.js mongodb mongoose


    【解决方案1】:

    据我所知,如果您为referedBy 路径提供异步验证函数,它会与预保存函数并行(有效)执行,而不是以阻止预保存函数的方式串行执行-保存函数的执行。

    您可以考虑将它们组合成一个函数,并且如果您想阻止更新被引用对象的被引用列表,直到满足电子邮件值的唯一约束(显然没有得到强制执行)直到实际的保存尝试),您可能希望将那一点逻辑粘贴在保存后挂钩中。

    干杯。

    编辑

    我已经通过多种方式查看了这一点,现在一切似乎都很清楚:

    1) 自定义验证函数在预保存挂钩之前执行,并且可以通过返回 false 来阻止预保存挂钩的执行(当然还有保存本身)。用以下方式证明:

    userSchema.pre('save', function (next) {
        console.log('EXECUTING PRE-SAVE');
        next();
    });
    
    userSchema.path('referredBy').validate(function (value, respond) {
        console.log('EXECUTING referredBy VALIDATION')
        respond(false);
    }, 'doesntExit');
    

    2) 不需要进行 db 查询来强制执行的内置验证器(例如“必需”约束)也会在预保存函数之前执行,并且可以阻止它们的执行。通过注释掉 b2 的电子邮件值分配而不是分配非唯一值来轻松演示:

    var b2 = new User();
    //b2.email = 'b';
    b2.referredBy = a._id;
    

    3) 确实需要进行数据库查询(例如强制唯一性)的内置验证器不会阻止预保存挂钩执行。据推测,这是为了优化成功案例,否则必须涉及 1 个查询来检查唯一性,然后在通过唯一性验证后进行另一个查询以进行 upsert。

    因此,验证(自定义或内置)确实在执行预保存挂钩之前发生,除了需要执行数据库查询的内置验证情况。

    【讨论】:

    • 是的,我想到了,但我试图避免组合验证,并希望有办法让它像宣传的那样工作
    • 做了更多的挖掘工作。似乎它主要以您期望的方式工作,除了内置唯一性验证器的情况。有关详细信息,请参阅我的更新答案。
    猜你喜欢
    • 2021-01-08
    • 2015-03-27
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 2016-04-29
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    相关资源
    最近更新 更多