我希望在保存特定模型自动后始终填充子文档。我真正想要的是下面的内容:
MyModel.post('save', function(doc, next) {
doc.populate('path').then(next);
});
然而,上述不会起作用,因为
post
中间件不直接接收流控制,例如没有next
或done
回调传递给它。post
挂钩是一种为这些方法注册传统事件监听器的方法。
当然,还有#34; Asynchronous Post Hooks",但它们仍然没有收到控制流,因此无法保证在我需要时会填充子文档。
为什么不使用嵌入式文档?对于这种情况,子文档与父文档相同(例如,我使用类似复合模式的东西),这将导致循环依赖,我不确定如何解决(或者如果我可以甚至解决它)。对于其他实例,我可能希望能够访问子文档而无需通过父文档。
我考虑的另一种方法是:
const nativeSave = /* ? */;
MyModel.methods.save = function save() {
return nativeSave.call(this).then(function(savedDoc) {
return savedDoc.populate('path');
});
};
然而,这有两个问题。首先,它看起来像一个圆形的解决方案。如果有一种更原生的方法并不违反猫鼬的实施,我宁愿这样做。其次,我不知道我将nativeSave
设置为什么(/* ? */
显示)。
那么,有什么建议来获得这种行为?如果我的第二个解决方案对于当前版本的mongoose来说是最好的,那么我应该将nativeSave
设置为什么?我已经考虑过嵌入式文档了,所以除非您提供有关解决循环依赖的建议,否则请不要建议使用它们。即便如此,我想为我提到的其他用例提供解决方案。
正如我的评论中所解释的那样,这与在其他帖子提出要求后保存后手动填充子文档不同。我希望这是自动发生的,以避免泄漏我的实现细节(例如使用ObjectIds而不是真实文档)。
答案 0 :(得分:2)
这将有助于
http://frontendcollisionblog.com/mongodb/2016/01/24/mongoose-populate.html
var bandSchema = new mongoose.Schema({
name: String,
lead: { type: mongoose.Schema.Types.ObjectId, ref: 'person' }
});
var autoPopulateLead = function(next) {
this.populate('lead');
next();
};
bandSchema.
pre('findOne', autoPopulateLead).
pre('find', autoPopulateLead);
var Band = mongoose.model('band', bandSchema);
答案 1 :(得分:1)
我要说的是,即使有可能使用内置的mongoose方法,例如" 保存"或" 查找"这可能是一个糟糕的主意。除了每次调用save方法都需要产生额外填充调用的开销之外,你肯定不想通过下载到底层的mongo驱动程序来改变函数的工作方式(你失去了验证) ,生命周期方法,如果你想使用mongoose文档,你必须为它重新查询数据库)。
您违反" 保存"以某种方式工作。各种各样的插件都不在桌面上,您可能会惊讶于任何追随您的开发人员。我不会在我负责的代码库中允许它。
因此,您将创建一个静态或架构方法。在该方法中,您将调用save,然后填充。像这样:
MyModel.methods.saveAndPopulate = function(doc) {
return doc.save().then(doc => doc.populate('foo').execPopulate())
}
这几乎是这里建议的最新方法:Mongoose populate after save。这就是我投票结束你的问题的原因。
答案 2 :(得分:1)
我实际上不会考虑这个"猴子修补"甚至不好的做法。当你想到它时,模型(例如上面的MyModel
例子)继承自mongoose的Model
类。因此,我认为它更多地扩展了基础save()
行为。这是维护者在覆盖mongoose方法和/或使用保留关键字时拒绝抛出错误的原因之一。
我更喜欢在可能的情况下封装数据,让实现成为外部世界的秘密。在这种情况下,我不希望任何外部代码知道我在数据库中使用ObjectId
。相反,我希望它认为我总是直接使用子文档。如果我泄漏了我的实现细节(使用ObjectId
s),我的代码将变得过于紧密耦合,从而使维护和更新成为一场噩梦。有很多资源可以更深入地了解封装及其好处。
此外,我相信SoC并模块化您的代码。我觉得让你的代码过于依赖Mongoose对save()
(或任何其他方法)的实现会使你的代码太脆弱而且Uncle Bob似乎同意。如果mongoose死了,我们切换到另一个DBMS,或者save()
的实现有了很大变化,如果我依赖Mongoose的实现细节,我们就会搞砸了。我喜欢我的代码与Mongoose和我尽可能使用的其他库分开。
如果这是一种普通的,多线程的OO语言,我们可能甚至不会进行这种讨论。只会继承save()
方法并阻塞,直到填充子文档。
扩展Mongoose方法的默认行为可能很危险,您应该非常谨慎。确保你了解自己在做什么,考虑了后果和替代方案,并进行了广泛的测试。您可能还想与您的团队讨论这种方法。
如果您有疑问和/或您认为Robert Moskal的解决方案足以满足您的需求,我建议您使用这种方法。事实上,我在很多模型中使用了类似的方法。
修改save()
会影响save()
的每个实例。如果不希望这样,请考虑Robert Moskal的方法。在我的情况下,我总是希望在保存后自动填充子文档。
这种方法也会对性能产生影响。如果您有许多深层嵌套的文档,这种方法可能不适合您。也许Robert Moskal在您的模型上返回Promises(或使用async
/ await
)的解决方案或定义方法更合适。就我而言,性能影响可以忽略不计并且可以接受。
此外,“意识形态”部分讨论的许多概念非常重要,适用于这种情况。这也表明这种方法是合适的。
正如我上面提到的,我认为MyModel
是继承自mongoose的Model
类。因此,我可以扩展MyModel
save()
方法的行为,就像扩展任何子类的继承方法的行为一样:
MyModel.methods.save = function save(options, callback) {
return mongoose.Model.prototype.save.call(this, options).then(function(savedDoc) {
return savedDoc.populate('path');
}).then(
function(populatedDoc) {
if(callback) {
callback(null, populatedDoc);
}
return populatedDoc;
},
function(error) {
if(callback) {
return callback(error);
}
throw error;
}
});
};
不幸的是,JS并没有真正拥有super
的概念,所以就像在JS中扩展的任何其他继承方法一样,我必须知道访问该方法的超类。除此之外,一切都非常简单直接。
答案 3 :(得分:0)
// extract only key of body
// const updates = Object.keys(req.body);
let model = Model.findById({_id}
// map automatically attributes
_.extend(congregation, req.body); // using lodash
// OR
// updates.forEach(update => {
// model[update] = req.body[update];
// });
await model.save().then(model => // without curly braces
model
.populate('a')
.populate('b')
.populate('c')
.populate('d')
.execPopulate(),
);
res.status(201).send(model);