25

I'm stuck on trying to get subdocument by _id in found document.

Example Schema

var User = mongoose.Schema({
        name:       String,
        photos:    [{src: String, title: String}]
    });
var Team = db.model('Team', Team);

Now I'm getting one user:

myUser = User.findOne(...)...

How can I get now src of his photo by it's _id (or title)?

Something like:

myUser.photos.findOne({'_id': myId})
gial
  • 344
  • 1
  • 5
  • 10

2 Answers2

61

You need to either create a NEW Schema for your embedded documents, or leave the type declaration as a blank array so mongoose interprets as a Mixed type.

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

-- OR --

var userSchema = new mongoose.Schema({
  name: String,
  photos: [photoSchema]
});

var photoSchema = new mongoose.Schema({
  src: String,
  title: String
});

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

And then you can save thusly:

var user = new User({
  name: 'Bob',
  photos: [ { src: '/path/to/photo.png' }, { src: '/path/to/other/photo.png' } ]
});

user.save();

From here, you can simply use array primitives to find your embedded docs:

User.findOne({name: 'Bob'}, function (err, user) {

  var photo = user.photos.filter(function (photo) {
    return photo.title === 'My awesome photo';
  }).pop();

  console.log(photo); //logs { src: '/path/to/photo.png', title: 'My awesome photo' }
});

-- OR --

You can use the special id() method in embedded docs to look up by id:

User.findOne({name: 'Bob'}, function (err, user) {
    user.photos.id(photo._id);
});

You can read more here: http://mongoosejs.com/docs/subdocs.html

Make sure you DON'T register the schema with mongoose, otherwise it will create a new collection. Also keep in mind that if the child documents are searched for often, it would be a good idea to use refs and population like below. Even though it hits the DB twice, its much faster because of indexing. Also, mongoose will bonk on double nesting docs (i.e. The children have children docs as well)

var user = mongoose.Schema({
  name: String,
  photos: [{ type: Schema.Types.ObjectId, ref: 'Photo' }]
});

var photo = mongoose.Schema({
  src: String,
  title: String
});

User
  .findOne({ name: 'foo' })
  .populate('photos')
  .exec(function (err, user) {
    console.log(user.photos[0].src);
  });

Relevant docs can be found here http://mongoosejs.com/docs/populate.html

srquinn
  • 10,134
  • 2
  • 48
  • 54
  • It is a good and right solution, but... Assumed that I already have found the user. And don't want to query the database again. – gial Jan 15 '14 at 16:27
  • That's it! Filter-solution is the best for me. Never used it before. Thanks! – gial Jan 17 '14 at 11:59
  • 1
    If your embedded documents don't have a schema, or they are of type `Mixed`, I don't believe that you can use the built-in `.id()` function as described above, since Mongoose doesn't know whether the data actually has an `_id` or not. In this case you should use the `.filter()` technique instead. – Tom Spencer May 15 '14 at 10:46
  • 1
    I know this is old, but I'd like to point out that the last note in this answer about using populate isn't really best practice (depending on your use case of course). jibsales says 'searched' often should be referenced, but for clarity it should be documents that are 'edited' often should be referenced. Small, but important distinction. – meteorainer Jan 06 '16 at 02:48
  • 1
    If you do not register the schema, how will mongoose connect the schema for Photo with user.Photos? – user1790300 Nov 04 '16 at 17:00
3

Adding to srquinn's answer, from my limited experience I thought populate was for joining documents from different collections together?

I think here you could just do User.findOne({ name: 'foo' }, 'photos') which is shorthand for:

const query = User.findOne({ name: 'foo' })
query.select('photos')
Dominic
  • 62,658
  • 20
  • 139
  • 163