Mongoose: Hydrate: restore populated data

Created on 17 Nov 2016  路  7Comments  路  Source: Automattic/mongoose

Currently when you use either new Model(data) or Model.hydrate(data) and data contains pre-populated stuff, the model instance won't have these populated fields.

Would it be possible to restore a model from a plain object while restoring populated data as well?

new feature

Most helpful comment

Thanks, that's quite clear. Would be really nice to have this done automatically.

All 7 comments

Not really supported at the moment. You'd have to call populated() on each path you want to populate, instantiate instances of the child model, and set each path.

@vkarpov15 any samples ready? Looking forward to 4.8 馃憤

Actually you don't need to do populated(), as long as you just instantiate child model instances it'll work

var userSchema = new Schema({
  name: String
});

var companySchema = new Schema({
  name: String,
  users: [{ ref: 'User', type: Schema.Types.ObjectId }]
});

var User = mongoose.model('User', userSchema);
var Company = mongoose.model('Company', companySchema);

var users = [User.hydrate({ _id: new mongoose.mongo.ObjectId(), name: 'Val' })];
var company = { _id: new mongoose.mongo.ObjectId(), name: 'Booster', users: [users[0]._id] };

// How to hydrate
var c = Company.hydrate(company);
c.users = users;

console.log(c.toObject({ virtuals: true }), c.populated('users'));
$ node gh-4727.js 
{ _id: 5838c535cd064350dab4d717,
  name: 'Booster',
  users: 
   [ { _id: 5838c535cd064350dab4d716,
       name: 'Val',
       id: '5838c535cd064350dab4d716' } ],
  id: '5838c535cd064350dab4d717' } [ 5838c535cd064350dab4d716 ]
^C
$ 

It's actually pretty easy, the general process is to hydrate the child models first, then hydrate the parent model, and set the desired paths to the hydrated child models

Thanks, that's quite clear. Would be really nice to have this done automatically.

Yeah it would be nice, but not a high priority atm. Till then, you can just do it manually

I ended up writing a small function to do this, could easily be adopted to support arrays as well:

/**
 * @method hydratePopulated
 * @param {Object} json
 * @param {Array} [populated]
 * @return {Document}
 */
Model.hydratePopulated = function(json, populated=[]) {
  let object = this.hydrate(json)
  for (let path of populated) {
    let { ref } = this.schema.paths[path].options
    object[path] = mongoose.model(ref).hydrate(json[path])
  }
  return object
}

I've written another version of it, arrays are still not supported, but this one attempt to automatically detect path that have been populated based on the schema. I didn't tested it much right now, but it seems to work pretty well.

const mongoose = require("mongoose");
const { getValue, setValue } = require("mongoose/lib/utils");

/**
 * @method hydratePopulated
 * @param {Object} json
 * @return {Document}
 */
mongoose.Model.hydratePopulated = function (json) {
  let object = this.hydrate(json);

  for (const [path, type] of Object.entries(this.schema.singleNestedPaths)) {
    const { ref } = type.options;
    if (!ref) continue;

    const value = getValue(path, json);
    if (value == null || value instanceof mongoose.Types.ObjectId) continue;

    setValue(path, mongoose.model(ref).hydratePopulated(value), object);
  }

  return object;
};

If anyone find a better way to get/set values (maybe in lib/helpers/populate?) that might do the trick for arrays too

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Soviut picture Soviut  路  3Comments

simonxca picture simonxca  路  3Comments

adamreisnz picture adamreisnz  路  3Comments

lukasz-zak picture lukasz-zak  路  3Comments

adamreisnz picture adamreisnz  路  3Comments