据我了解,在将文档插入集合之前但是在验证发生之后,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
});
});
});
});
其他所有内容都会检出,错误被抛出,失败发生,但所有预保存挂钩都会被保存
知道如何修复此问题,或者验证挂钩后是否存在真正的预保存?
答案 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>的