mongoose unique:验证之前的true pre-save hook调用

时间:2014-02-21 00:56:40

标签: node.js mongodb mongoose

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

就我而言,无论如何都会被召唤:

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

因此用户注册时没有推荐 - >行

用户b注册a作为推荐 - >行

用户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
            });
        });
    });
});

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

知道如何修复此问题,或者验证挂钩后是否存在真正的预保存?

1 个答案:

答案 0 :(得分:2)

据我所知,如果你为referBy路径提供一个异步验证函数,它将与预保存函数并行(有效)执行,而不是以这样的方式串行执行它会阻止pre -save函数的执行。

您可以考虑将它们合并到一个函数中,如果您想要阻止更新referBy对象的引用列表,例如,之后已经满足电子邮件值的唯一约束(显然没有得到强制执行)直到实际的保存尝试),你可能想在保存后的钩子中坚持这一点逻辑。

干杯。

修改

我已经从很多方面看了这一点,而且在这一点上看起来都很清楚:

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查询(例如“required”约束)也会在预保存函数之前执行,并且可以阻止它们的执行。通过评论b2的电子邮件值分配而不是分配非唯一值来轻松演示:

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

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

因此,验证(自定义或内置)确实在执行预保存挂钩之前发生,除了内置验证需要db查询强制执行< / em>的