Mongoose でネストした populate の書き方

Mongoose の 3.6 から ネストした populate に対応したそうなので動作確認を試みたのですが、[GitHub の Issue で問い合わせ](https://github.com/LearnBoost/mongoose/issues/1377)をするまでやり方が分かりませんでした。

他の方もハマるかもしれないので、ここにまとめておきます。

## 誤った書き方

最初、以下のようなコードを書いていました。

```JSON:package.json
{
  "name": "mongoose-populate-test",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "mongoose": "3.6.4",
    "async": "0.1.22"
  },
  "engines": {
    "node": ">=0.10.2",
    "npm": ">=1.2.15"
  },
  "scripts": {
    "start": "node test.js"
  }
}
```

```javascript: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();
      });
    });
});
```

```text:OUTPUT
===========
    mongoose version: 3.6.4
========


dbname: testing_populateAdInfinitum
{ title: 'blog 0',
  author:
   { _id: 5194634c2860ba0824000003,
     name: 'mary',
     phone: null,
     __v: 0 },
  _id: 5194634c2860ba0824000005,
  tags: [ 'fun', 'cool' ],
  __v: 0 }
```

OUTPUT の author.phone が以下のように出力されることを期待していましたが、上記のとおり、該当の箇所は null として出力されてしまいました。

```JSON
phone {
  _id: xxxxxxxxxxxxxxxx,
  name: 'iPhone 5'
}
```

## 正しい書き方

問題のあるコードは以下の箇所です。

```javascript
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);
    });
  });
};
```

この部分を以下のどちらかに書き換えると、正常に動作します。

```javascript:1
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',
      model: Phone
    };

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

```javascript:2
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',
    };

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

## 参照

* Can't make nested populate in new mongoose 3.6rc0
    * https://github.com/LearnBoost/mongoose/issues/1377