Mongoose: Populate a populated field
Asked Answered
C

6

19

I'm using MongoDB as a log keeper for my app to then sync mobile clients. I have this models set up in NodeJS:

var UserArticle = new Schema({
    date: { type: Number, default: Math.round((new Date()).getTime() / 1000) }, //Timestamp!
    user: [{type: Schema.ObjectId, ref: "User"}],
    article: [{type: Schema.ObjectId, ref: "Article"}],
    place: Number,    
    read: Number,     
    starred: Number,   
    source: String
});
mongoose.model("UserArticle",UserArticle);

var Log = new Schema({
    user: [{type: Schema.ObjectId, ref: "User"}],
    action: Number, // O => Insert, 1 => Update, 2 => Delete
    uarticle: [{type: Schema.ObjectId, ref: "UserArticle"}],
    timestamp: { type: Number, default: Math.round((new Date()).getTime() / 1000) }
});
mongoose.model("Log",Log);

When I want to retrive the log I use the follwing code:


var log = mongoose.model('Log');
log
.where("user", req.session.user)
.desc("timestamp")
.populate("uarticle")
.populate("uarticle.article")
.run(function (err, articles) {
if (err) {
    console.log(err);
        res.send(500);
    return;
}
res.json(articles);

As you can see, I want mongoose to populate the "uarticle" field from the Log collection and, then, I want to populate the "article" field of the UserArticle ("uarticle").

But, using this code, Mongoose only populates "uarticle" using the UserArticle Model, but not the article field inside of uarticle.

Is it possible to accomplish it using Mongoose and populate() or I should do something else?

Thank you,

Concision answered 17/12, 2011 at 9:53 Comment(1)
I've run into the same problem where the reference is embedded in an array -> myList: [{ mid: {type:Schema.ObjectId, 'ref':'OtherModel'}, meta: [String]}]. This produces the following error when I try .populate('myList.mid')... TypeError: Cannot call method 'path' of undefinedHrvatska
S
15

From what I've checked in the documentation and from what I hear from you, this cannot be achieved, but you can populate the "uarticle.article" documents yourself in the callback function.

However I want to point out another aspect which I consider more important. You have documents in collection A which reference collection B, and in collection B's documents you have another reference to documents in collection C.

You are either doing this wrong (I'm referring to the database structure), or you should be using a relational database such as MySQL here. MongoDB's power relies in the fact you can embed more information in documents, thus having to make lesser queries (having your data in a single collection). While referencing something is ok, having a reference and then another reference doesn't seem like you're taking the full advantage of MongoDB here.

Perhaps you would like to share your situation and the database structure so we could help you out more.

Sibilate answered 17/12, 2011 at 11:7 Comment(3)
The Logs collection is the only one that has weird references, I just wanted, because the others have only 1 reference (to avoid repeating a lot of times the same data). I think that to avoid using a lot of information, I'll not populate this the "uarticle" and the client will make a request to get the details of the Article, just to keep things simple and fast.Concision
Can you give more details on your answer -- am I reading it right, that having even one reference should be examined. I'm just getting started in mongodb. After all, a lot of relational tables have only one foreign key. Are embedded documents preferable to populating ObjectId refs?Histochemistry
It really depends on the structure of the project, sometimes you may even want to duplicate data (embed) for speed, other times you probably need to reference it, because that data will be queried for only in certain conditions.Sibilate
F
6

You can use the mongoose-deep-populate plugin to do this. Usage:

User.find({}, function (err, users) {
   User.deepPopulate(users, 'uarticle.article', function (err, users) {
      // now each user document includes uarticle and each uarticle includes article
   })
})

Disclaimer: I'm the author of the plugin.

Firelock answered 10/12, 2014 at 19:57 Comment(0)
C
6

I faced the same problem,but after hours of efforts i find the solution.It can be without using any external plugin:)

    applicantListToExport: function (query, callback) {
      this
       .find(query).select({'advtId': 0})
       .populate({
          path: 'influId',
          model: 'influencer',
          select: { '_id': 1,'user':1},
          populate: {
            path: 'userid',
            model: 'User'
          }
       })
     .populate('campaignId',{'campaignTitle':1})
     .exec(callback);
    }
Crittenden answered 12/6, 2017 at 22:25 Comment(0)
C
1

Mongoose v5.5.5 seems to allow populate on a populated document.

You can even provide an array of multiple fields to populate on the populated document

var batch = await mstsBatchModel.findOne({_id: req.body.batchId})
  .populate({path: 'loggedInUser', select: 'fname lname', model: 'userModel'})
  .populate({path: 'invoiceIdArray', model: 'invoiceModel', 
     populate: [
       {path: 'updatedBy', select: 'fname lname', model: 'userModel'},
       {path: 'createdBy', select: 'fname lname', model: 'userModel'},
       {path: 'aircraftId', select: 'tailNum', model: 'aircraftModel'}
     ]});
Christner answered 17/12, 2019 at 16:31 Comment(0)
F
0

how about something like:

populate_deep = function(type, instance, complete, seen)
{
  if (!seen)
    seen = {};
  if (seen[instance._id])
  {
    complete();
    return;
  }
  seen[instance._id] = true;
  // use meta util to get all "references" from the schema
  var refs = meta.get_references(meta.schema(type));
  if (!refs)
  {
    complete();
    return;
  }
  var opts = [];
  for (var i=0; i<refs.length; i++)
    opts.push({path: refs[i].name, model: refs[i].ref});
  mongoose.model(type).populate(instance, opts, function(err,o){
    utils.forEach(refs, function (ref, next) {
      if (ref.is_array)
        utils.forEach(o[ref.name], function (v, lnext) {
          populate_deep(ref.ref_type, v, lnext, seen);
        }, next);
      else
        populate_deep(ref.ref_type, o[ref.name], next, seen);
    }, complete);
  });
}

meta utils is rough... want the src?

Forge answered 6/3, 2014 at 20:10 Comment(0)
T
0

or you can simply pass an obj to the populate as:

const myFilterObj = {};
const populateObj = {
                path: "parentFileds",
                populate: {
                    path: "childFileds",
                    select: "childFiledsToSelect"
                },
                select: "parentFiledsToSelect"
               };
Model.find(myFilterObj)
     .populate(populateObj).exec((err, data) => console.log(data) );

Tatiania answered 16/1, 2020 at 10:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.