保存

时间:2017-07-11 20:41:29

标签: mongoose

我希望在保存特定模型自动后始终填充子文档。我真正想要的是下面的内容:

MyModel.post('save', function(doc, next) {
    doc.populate('path').then(next);
});

然而,上述不会起作用,因为

  

post中间件不直接接收流控制,例如没有nextdone回调传递给它。 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而不是真实文档)。

4 个答案:

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