Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't make nested populate in new mongoose 3.6rc0 #1377

Closed
skotchio opened this issue Mar 10, 2013 · 19 comments
Closed

Can't make nested populate in new mongoose 3.6rc0 #1377

skotchio opened this issue Mar 10, 2013 · 19 comments

Comments

@skotchio
Copy link

I have the following schemas:

var DocumentSchema = new Schema({
  title    : {type: String},
  emails   : [{type: ObjectId, ref: 'Emails'}]
});

var EmailSchema = new Schema({
  title    : {type: String},
  tags     : [{type: ObjectId, ref: 'Tags'}]
});

var TagSchema = new Schema({
  title    : {type: String}    
});

I want to find a document with populated 'emails' and tags. When I try this:

Document.findById(ObjectId('...'))
  populate('emails.tags')
  .exec(function (err, document) {

  });

I get the error:

Caught exception: TypeError: Cannot call method 'path' of undefined
@aheckmann
Copy link
Collaborator

Works as designed. See the Model.populate() section in the pre-release notes

@andyburke
Copy link

Can you clarify how this is works as designed? I can't find anything in the docs referenced that shows me how I should approach this. I'm getting the same error.

@teloo
Copy link

teloo commented Mar 29, 2013

Can't make nested populate in new mongoose v3.6.
I don't understand that "author.phone" is null.

Do I mistake it?

output


===========
    mongoose version: 3.6.0
========


dbname: testing_populateAdInfinitum
{ title: 'blog 0',
  author:
   { _id: 51559f6ad1bc1d8c1f000003,
     name: 'mary',
     phone: null,
     __v: 0 },
  _id: 51559f6ad1bc1d8c1f000007,
  tags: [ 'fun', 'cool' ],
  __v: 0 }

package.json

{
  "name": "mongoose-populate-test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "mongoose": "3.6.0",
    "async": "0.1.22"
  },
  "engines": {
    "node": ">=0.8.21",
    "npm": ">=1.2.11"
  },
  "scripts": {
    "start": "node test.js"
  }
}

test.js

var assert = require('assert');
var mongoose = require('mongoose');
var async = require('async');
var Schema = mongoose.Schema;
var ObjectId = mongoose.Types.ObjectId;

console.log('\n===========');
console.log('    mongoose version: %s', mongoose.version);
console.log('========\n\n');

var dbname = 'testing_populateAdInfinitum';
console.log('dbname: %s', dbname);
mongoose.connect('localhost', dbname);
mongoose.connection.on('error', function () {
  console.error('connection error', arguments);
});

var phone = new Schema({
    name: String
});
var Phone = mongoose.model('Phone', phone);

var user = new Schema({
    name: String
  , phone: { type: Schema.ObjectId, ref: 'Phone' }
});
var User = mongoose.model('User', user);

var blogpost = Schema({
    title: String
  , tags: [String]
  , author: { type: Schema.ObjectId, ref: 'User' }
});
var BlogPost = mongoose.model('BlogPost', blogpost);

var createPhones = function(callback) {
  var phoneIds = [new ObjectId, new ObjectId];
  var phones = [];

  phones.push({
    _id: phoneIds[0]
    , name: 'iPhone 5'
  });
  phones.push({
    _id: phoneIds[1]
    , name: 'GALAXY S'
  });

  Phone.create(phones, function(err, docs) {
    assert.ifError(err);
    callback(null, phoneIds);
  });
};

var createUsers = function(phoneIds, callback) {
  var userIds = [new ObjectId, new ObjectId];
  var users = [];

  users.push({
    _id: userIds[0]
    , name: 'mary'
    , phone: phoneIds[0]
  });
  users.push({
    _id: userIds[1]
    , name: 'bob'
    , phone: phoneIds[1]
  });

  User.create(users, function(err, docs) {
    assert.ifError(err);
    callback(null, userIds);
  });
};

var createBlogPosts = function(userIds, callback) {
  var blogposts = [];

  blogposts.push({ title: 'blog 0', tags: ['fun', 'cool'], author: userIds[0] });
  blogposts.push({ title: 'blog 1', tags: ['cool'], author: userIds[1] });

  BlogPost.create(blogposts, function(err, docs) {
    assert.ifError(err);
    callback(null);
  });
};

var findFunBlogPosts = function(callback) {
  BlogPost.find({ tags: 'fun' }).lean().populate('author').exec(function (err, docs) {
    assert.ifError(err);

    var opts = {
        path: 'author.phone'
      , select: 'name'
    };

    BlogPost.populate(docs, opts, function(err, docs) {
      assert.ifError(err);
      docs.forEach(function(doc) {
        console.log(doc);
      });
      callback(null);
    });
  });
};

mongoose.connection.on('open', function () {
  async.waterfall([
      createPhones, 
      createUsers, 
      createBlogPosts,  
      findFunBlogPosts
    ], 
    function(err, result) {
      if (err) {
        console.error(err.stack);
      }
      mongoose.connection.db.dropDatabase(function () {
        mongoose.connection.close();
      });
    });
});

@kuba-kubula
Copy link

There is a context aware part of your code:
var phoneIds = [… should be 1) var outside of the func and 2) in this function only push to this array.

@yangsu
Copy link

yangsu commented Apr 4, 2013

I ran into the same problem. I think it has to do with the fact that even when the first populate call has completed, the BlogPost model still doesn't understand the query 'authors.phone' because it wasn't defined in the BlogPost Schema. Think this is the designed behavior.

For now, this is how I resolved it. I've made changes to your code as @kuba-kubula pointed out. And I extracted the authors out and then used a populate call on User to populate phone.

Here's the code and the revisions view to see the changes I made.

Hope this helps.

@aheckmann
Copy link
Collaborator

@yangsu you are correct. Though there is another way. Either specify the model that should be used for population in the options to User.populate or use the Phone model directly:

BlogPost.find({ tags: 'fun' }).lean().populate('author').exec(function (err, docs) {
  assert.ifError(err);

  User.populate(docs, {
    path: 'author.phone',
    select: 'name',
    model: Phone // <== We are populating phones so we need to use the correct model, not User
  }, callback);

  // or use the model directly

  Phone.populate(docs, {
    path: 'author.phone',
    select: 'name',
  }, callback);

This seems clunky. We should be able to do better with this in a future release. If the documents being populated are mongoose documents (opposed to plain objects) we could possibly use it's related schema to determine which Model to use. Won't work for plain objects of course.

@skotchio
Copy link
Author

skotchio commented Apr 5, 2013

#1381

@teloo
Copy link

teloo commented Apr 5, 2013

@kuba-kubula @yangsu @aheckmann
Thank you for replying, I resolved it.

@burakkilic
Copy link

@vovan22 Did you find any solution? I have the same problem. If I want to populate subcategories.products and I got the same error. #1568

@skotchio
Copy link
Author

skotchio commented Jul 4, 2013

BlogPost.find({ tags: 'fun' }).lean().populate('author').exec(function (err, docs) {
assert.ifError(err);

User.populate(docs, {
path: 'author.phone',
select: 'name',
model: Phone // <== We are populating phones so we need to use the correct model, not User
}, callback);

// or use the model directly

Phone.populate(docs, {
path: 'author.phone',
select: 'name',
}, callback);

@toddpi314
Copy link

The reverse populate off of the model itself is pretty nice. Thanks for this.

@mikepc
Copy link

mikepc commented Sep 1, 2014

exports.padByID = function(req, res, next, id) {

 Pad.findById(id).populate('comments_pub').exec(function(err, pad) {
    if (err) return next(err);
    User.populate(pad.comments_pub,
                    { path : 'comments_pub.user',
                      select: 'email',
                      model: 'User'});

    if (! pad) return next(new Error('Failed to load Pad ' + id));
    req.pad = pad ;
    next();
});

};

This is simply not working for me :(. I cannot get populate() to work for me correctly, any help would be appreciated.

The comments_pub Pad property is an array of generic objects, each of which has a User model associated to it.

@JaKXz
Copy link

JaKXz commented May 29, 2015

@aheckmann I've got the callback function returning the updated model object, which is great. However, in this context:

var User = mongoose.model('User');
var Grade = mongoose.model('Grade');

User.findOne({ ... })
  .populate('array')
  .exec(function (err, user) {
    Grade.populate(user, {
      path: 'array.grades',
      select: 'name -_id'
    }, function (error, updatedUser) {
      //assuming no error,
      user = updatedUser; //how do I persist / pass this on synchronously?
    });
  });

how do I get the user object returned from my findOne query to get the nested property data? I'm using Mongoose 4.0.x - and I've looked on S/O and through the docs of course, but if I've missed some built in way of doing it would be nice to know.

@vkarpov15
Copy link
Collaborator

@JaKXz what do you mean by persisting? You should be able to push/pull from the user.grades array.

@ezequielzacca
Copy link

As the metadata for population is present in the ref property, shouldnt the libary be able to automatically guess the correct model for population?

@vkarpov15
Copy link
Collaborator

I don't understand your question, please clarify

@gregballot
Copy link

I think he's asking if the field you are trying to use to populate is correctly defined the Model schema.

@usernamedoesntexist
Copy link

@yangsu you are correct. Though there is another way. Either specify the model that should be used for population in the options to User.populate or use the Phone model directly:

BlogPost.find({ tags: 'fun' }).lean().populate('author').exec(function (err, docs) {
  assert.ifError(err);

  User.populate(docs, {
    path: 'author.phone',
    select: 'name',
    model: Phone // <== We are populating phones so we need to use the correct model, not User
  }, callback);

  // or use the model directly

  Phone.populate(docs, {
    path: 'author.phone',
    select: 'name',
  }, callback);

This seems clunky. We should be able to do better with this in a future release. If the documents being populated are mongoose documents (opposed to plain objects) we could possibly use it's related schema to determine which Model to use. Won't work for plain objects of course.

thanks you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests