Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically Populate Subdocuments After Saving

Tags:

mongoose

I would like to always populate subdocuments after saving a particular Model automatically. What I would really like is something like below:

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

However, the above won't work because

post middleware do not directly receive flow control, e.g. no next or done callbacks are passed to it. post hooks are a way to register traditional event listeners for these methods.

Of course, there are "Asynchronous Post Hooks", but they still do not receive control flow so there is no guarantee the subdocuments will be populated when I need it.

Why not just use an embedded document? For this case, the subdocuments are the same as the parent document (e.g. I'm using something like the Composite pattern), which would cause a circular dependency that I'm not certain how to resolve (or if I can even resolve it). For other instances, I might want to be able to access the subdocuments without going through the parent document.

Another approach I considered is something like:

const nativeSave = /* ? */;

MyModel.methods.save = function save() {
    return nativeSave.call(this).then(function(savedDoc) {
        return savedDoc.populate('path');
    });
};

However, there are two problems with this. First of all, it seems a like a round-about solution. If there is a more native approach that doesn't go against mongoose's implementation, I would prefer that. Secondly, I don't know what I would set nativeSave to (as apparent by the /* ? */).

So, what are some suggestions for getting this behavior? If my second solution would be the best with the current version of mongoose, what should I set nativeSave to? I've already considered embedded documents, so please don't suggest using them unless you are providing a suggestion about resolving the circular dependency. Even then, I would like a solution for the other use-case I mentioned.

As explained in my comment, this is not the same as manually populating a subdocument after saving as other posts have asked. I want this to happen automatically to avoid leaking my implementation details (e.g. using ObjectIds instead of real documents).

like image 318
c1moore Avatar asked Dec 19 '22 06:12

c1moore


2 Answers

I'm going to say that even if it were possible to monkeypatch a built in mongoose method like "save" or "find" it would probably be a terrible idea. Aside from the fact that not every call to the save method needs to incur the overhead of the extra populate call, you certainly wouldn't want to change the way the function works by dropping down to the underlying mongo driver (you lose validations, life cycle methods, and if you want to work with a mongoose document, you'll have to requery the database for it).

You run a huge risk breaking any code that depends on "save" working a certain way. All sorts of plugins are off the table, and you risk astonishing any developers that come after you. I wouldn't allow it in a codebase I was responsible for.

So you are left with create a static or schema method. In that method you'll call save, followed by populate. Something like this:

MyModel.methods.saveAndPopulate = function(doc) {
  return doc.save().then(doc => doc.populate('foo').execPopulate())
}

That's pretty much the most up to date approach suggested here: Mongoose populate after save. That's why I voted to close your question.

like image 162
Robert Moskal Avatar answered Jan 09 '23 03:01

Robert Moskal


This will help

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);
like image 38
David Ghulijanyan Avatar answered Jan 09 '23 02:01

David Ghulijanyan