Mongoose populate after save

node.jsMongodbMongoosePopulate

node.js Problem Overview


I cannot manually or automatically populate the creator field on a newly saved object ... the only way I can find is to re-query for the objects I already have which I would hate to do.

This is the setup:

var userSchema = new mongoose.Schema({   
  name: String,
});
var User = db.model('User', userSchema);

var bookSchema = new mongoose.Schema({
  _creator: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  description: String,
});
var Book = db.model('Book', bookSchema);

This is where I am pulling my hair

var user = new User();
user.save(function(err) {
    var book = new Book({
        _creator: user,
    });
    book.save(function(err){
        console.log(book._creator); // is just an object id
        book._creator = user; // still only attaches the object id due to Mongoose magic
        console.log(book._creator); // Again: is just an object id
        // I really want book._creator to be a user without having to go back to the db ... any suggestions?
    });
});

EDIT: latest mongoose fixed this issue and added populate functionality, see the new accepted answer.

node.js Solutions


Solution 1 - node.js

You should be able to use the Model's populate function to do this: http://mongoosejs.com/docs/api.html#model_Model.populate In the save handler for book, instead of:

book._creator = user;

you'd do something like:

Book.populate(book, {path:"_creator"}, function(err, book) { ... });

Probably too late an answer to help you, but I was stuck on this recently, and it might be useful for others.

Solution 2 - node.js

The solution for me was to use execPopulate, like so

const t = new MyModel(value)
return t.save().then(t => t.populate('my-path').execPopulate())

Solution 3 - node.js

In case that anyone is still looking for this.

Mongoose 3.6 has introduced a lot of cool features to populate:

book.populate('_creator', function(err) {
 console.log(book._creator);
});

or:

Book.populate(book, '_creator', function(err) {
 console.log(book._creator);
});

see more at: https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes#population

But this way you would still query for the user again.

A little trick to accomplish it without extra queries would be:

book = book.toObject();
book._creator = user;

Solution 4 - node.js

The solution which returns a promise (no callbacks):

Use Document#populate

book.populate('creator').execPopulate();

// summary
doc.populate(options);               // not executed
doc.populate(options).execPopulate() // executed, returns promise

Possible Implementation

var populatedDoc = doc.populate(options).execPopulate();
populatedDoc.then(doc => {
   ... 
});

Read about document population here.

Solution 5 - node.js

Just to elaborate and give another example, as it helped me out. This might help those who want to to retrieve partially populated objects after save. The method is slightly different as well. Spent more than an hour or two looking for the correct way to do it.

  post.save(function(err) {
    if (err) {
      return res.json(500, {
        error: 'Cannot save the post'
      });
    }
    post.populate('group', 'name').populate({
      path: 'wallUser',
      select: 'name picture'
    }, function(err, doc) {
      res.json(doc);
    });
  });

Solution 6 - node.js

I thought I'd add to this to clarify things for complete noobs like myself.

What's massively confusing if you're not careful is that there are three very different populate methods. They are methods of different objects (Model vs. Document), take different inputs and give different outputs (Document vs. Promise).

Here they are for those that are baffled:

Document.prototype.populate()

See full docs.

This one works on documents and returns a document. In the original example, it would look like this:

book.save(function(err, book) {
    book.populate('_creator', function(err, book) {
        // Do something
    })
});

Because it works on documents and returns a document, you can chain them together like so:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */', function(err, book) {
        // Do something
    })
});

But don't be silly, like me, and try to do this:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */')
    .then(function(book) {
        // Do something
    })
});

Remember: Document.prototype.populate() returns a document, so this is nonsense. If you want a promise, you need...

Document.prototype.execPopulate()

See full docs.

This one works on documents BUT it returns a promise that resolves to the document. In other words, you can use it like this:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */')
    .execPopulate()
    .then(function(book) {
        // Do something
    })
});

That's better. Finally, there's...

Model.populate()

See full docs.

This one works on models and returns a promise. It's therefore used a bit differently:

book.save(function(err, book) {
    Book // Book not book
    .populate(book, { path: '_creator'})
    .then(function(book) {
        // Do something
    })
});

Hope that's helped some other newcomers.

Solution 7 - node.js

Unfortunetly this is a long standing issue with mongoose which I believe is not solved yet:

https://github.com/LearnBoost/mongoose/issues/570

What you can do is to write you own custom getter/setter ( and set real _customer in a seperate property ) for this. For example:

var get_creator = function(val) {
    if (this.hasOwnProperty( "__creator" )) {
        return this.__creator;
    }
    return val;
};
var set_creator = function(val) {
    this.__creator = val;
    return val;
};
var bookSchema = new mongoose.Schema({
  _creator: {
     type: mongoose.Schema.Types.ObjectId,
     ref: 'User',
     get: get_creator,
     set: set_creator
  },
  description: String,
});

NOTE: I didn't test it and it might work strangely with .populate and when setting pure id.

Solution 8 - node.js

Mongoose 5.2.7

This works for me (just a lot of headache !)

exports.create = (req, res, next) => {
  const author = req.userData;
  const postInfo = new Post({
    author,
    content: req.body.content,
    isDraft: req.body.isDraft,
    status: req.body.status,
    title: req.body.title
  });
  postInfo.populate('author', '_id email role display_name').execPopulate();
  postInfo.save()
    .then(post => {
      res.status(200).json(post);
    }).catch(error => {
      res.status(500).json(error);
    });
};

Solution 9 - node.js

Save document in model and then populate

   chatRoom = await chatRoom.save();
   const data = await chatRoom
  .populate("customer", "email dp")
  .populate({
    path: "admin",
    select: "name logo",
  })
  .execPopulate();

Solution 10 - node.js

Probably sth. like

Book.createAsync(bookToSave).then((savedBook) => savedBook.populateAsync("creator"));

Would be the nicest and least problematic way to make this work (Using Bluebird promises).

Solution 11 - node.js

ended up writing some curry-able Promise functions where you declare your schema, query_adapter, data_adapter functions and populate string in advance. For a shorter / simpler implementation easier on.

It's probably not super efficient, but I thought the execution bit was quite elegant.

github file: curry_Promises.js

declartion

const update_or_insert_Item = mDB.update_or_insert({
    schema : model.Item,
    fn_query_adapter : ({ no })=>{return { no }},
    fn_update_adapter : SQL_to_MDB.item,
    populate : "headgroup"
    // fn_err : (e)=>{return e},
    // fn_res : (o)=>{return o}
})

execution

Promise.all( items.map( update_or_insert_Item ) )
.catch( console.error )
.then( console.log )

Solution 12 - node.js

It's worked for me

    let post = await PostModel.create({
        ...req.body, author: userId
    })

    post = await post.populate('author', 'name')

    res.status(200).json({
        status: 'success',
        data: { post }
    })

Solution 13 - node.js

In Mongoose 6.x you can simply do:

const t = await MyModel.create(value).then((t) =>
  t.populate('my-path')
);

The execPopulate() method has now been removed.

Solution 14 - node.js

Nothing worked for me so I saved and then used findById to get that data with populate back again

const data: any = await Model.findById(id)
  .populate("name")
  

Solution 15 - node.js

I'm not adding anything new here.

It's just a cleaner way of writing this using async/await:

const newCourse = new Course(new_course_data);
const populate_options = [
  // Here write your populate options
];
const created_course = await newCourse.save();
await created_course.populate(populate_options).execPopulate();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPyklerView Question on Stackoverflow
Solution 1 - node.jsuser1417684View Answer on Stackoverflow
Solution 2 - node.jsFrançois RomainView Answer on Stackoverflow
Solution 3 - node.jsEric SaboiaView Answer on Stackoverflow
Solution 4 - node.jsGovind RaiView Answer on Stackoverflow
Solution 5 - node.jsPratik BothraView Answer on Stackoverflow
Solution 6 - node.jscurzmgView Answer on Stackoverflow
Solution 7 - node.jsfreakishView Answer on Stackoverflow
Solution 8 - node.jsWhisherView Answer on Stackoverflow
Solution 9 - node.jsSehrish WaheedView Answer on Stackoverflow
Solution 10 - node.jskboomView Answer on Stackoverflow
Solution 11 - node.jsWouldBeNerdView Answer on Stackoverflow
Solution 12 - node.jsMinh ToànView Answer on Stackoverflow
Solution 13 - node.jsEd HollinghurstView Answer on Stackoverflow
Solution 14 - node.jsSehrish WaheedView Answer on Stackoverflow
Solution 15 - node.jsAG_HIHIView Answer on Stackoverflow