Population

findById comes with an options parameter that can have one property populate that can be used to dictate if and how we want to get any embedded subdocuments from the database. If populate option is true all embedded subdocuments are retrieved from the database.

From our "Embedded Documents" example, if we were to retrieve the user document created:

User.findById(userId, {populate: true}, function(err, user) {
  console.log(user instanceof User) // true
  console.log(user.address instanceof Address) // true
  console.log(user.posts[0] instanceof BlogPost) // true

  console.log(user) // full user document with retrieved address and posts subdocuments
})

We can specify a single field to populate:

User.findById(userId, {populate: 'address'}, function(err, user) {
  console.log(user instanceof User) // true
  console.log(user.address instanceof Address) // true
  console.log(user.posts[0] instanceof BlogPost) // false
  console.log(typeof user.posts[0] === 'string') // true - posts is an array of string keys
})
User.findById(userId, {populate: 'posts'}, function(err, user) {
  console.log(user instanceof User) // true
  console.log(user.address instanceof Address) // false
  console.log(typeof user.address === 'string') // true
  console.log(user.posts[0] instanceof BlogPost) // true
})

Similarly this can also be accomplished by passing { populate: { path: 'address' } } as options. We can explicitly specify array indexes to populate

User.findById(userId, {populate: 'posts.1'}, function(err, user) {
  console.log(user instanceof User) // true
  console.log(user.address instanceof Address) // false
  console.log(user.posts[0] instanceof BlogPost) // false
  console.log(typeof user.posts[0] === 'string') // true
  console.log(user.posts[1] instanceof BlogPost) // true - fully populated
})

Additionally, populate can accept an array if fields to populate:

User.findById(userId, {populate: ['address', 'posts.1']}, function(err, user) {
  console.log(user instanceof User) // true
  console.log(user.address instanceof Address) // true - fully populated
  console.log(user.posts[0] instanceof BlogPost) // false
  console.log(typeof user.posts[0] === 'string') // true
  console.log(user.posts[1] instanceof BlogPost) // true - fully populated
})

A special use case might be that we want to populate path foo into a target field bar. This can be accomplished by specifying a target populate option. For example if we have the following models:

var profileSchema = lounge.schema({
  firstName: String,
  lastName: String,
  email: String
})

Profile = lounge.model('Profile', profileSchema)

var ticketSchema = lounge.schema({
  confirmationCode: String,
  profileId: Profile,
  profile: Object
})

Ticket = lounge.model('Ticket', ticketSchema)

We can do:

Ticket.findById(ticketId, { populate: { path: 'profileId', target:'profile' } }, function(err, ticket) {
  console.log(ticket)
})

Sample output:

{ confirmationCode: 'ClqwgiWea',
  profileId: '366f4088-8dc6-4223-a418-495ad51d0436',
  profile:
   { firstName: 'Thomas',
     lastName: 'Kennedy',
     email: 'tkennedy1@walmart.com',
     id: '366f4088-8dc6-4223-a418-495ad51d0436' } }