Californian
Californian

Reputation: 1087

Saving and Finding Mongoose Documents with Nested Schemata with Refs and Promises

I have a fairly simple question. I'm trying to save a document whose schema includes a nested schema ref, and that schema ref includes another schema ref. When I go to retrieve that document, however, it does not include the (required) nested field unless I populate it in the same query. Even when I populate the query, however, the second nested document is not populated. Am I misunderstanding something fundamental here about the way refs work in mongoose?

JavaScript and LiveScript example code and output below.


JavaScript:

(function(){
  var mongoose, bookSchemaObj, authorSchemaObj, agentSchemaObj, bookSchema, authorSchema, agentSchema, Book, Author, Agent, testBookObj, testAuthorObj, testAgentObj, testAgent, testAuthor, testBook;
  mongoose = require("mongoose-bird")(require("mongoose"));
  mongoose.connect("mongodb://test:test@localhost:27017/test");
  bookSchemaObj = {
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Author",
      required: true
    },
    pubYear: {
      type: Number
    }
  };
  authorSchemaObj = {
    name: {
      type: String,
      required: true
    },
    agent: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "Agent",
      required: true
    }
  };
  agentSchemaObj = {
    name: {
      type: String,
      required: true
    }
  };
  bookSchema = new mongoose.Schema(bookSchemaObj);
  authorSchema = new mongoose.Schema(authorSchemaObj);
  agentSchema = new mongoose.Schema(agentSchemaObj);
  Book = mongoose.model("Book", bookSchema);
  Author = mongoose.model("Author", authorSchema);
  Agent = mongoose.model("Agent", agentSchema);
  testBookObj = {
    pubYear: 2001
  };
  testAuthorObj = {
    name: "John P. Doe"
  };
  testAgentObj = {
    name: "Alfred O. Thompson"
  };
  testAgent = new Agent(testAgentObj);
  testAuthor = new Author(testAuthorObj);
  testBook = new Book(testBookObj);
  testAuthor.agent = testAgent._id;
  testBook.author = testAuthor._id;
  testAgent.saveAsync().then(function(){
    return testAuthor.saveAsync();
  }).then(function(){
    return testBook.saveAsync();
  }).then(function(){
    console.log("book after saving agent, author, and book:");
    console.log(JSON.stringify(testBook, undefined, 2));
    console.log("");
    return Author.findById(testAuthor._id).populate("agent").execAsync();
  }).then(function(queriedAuthor){
    console.log("author after finding and populating author");
    console.log(JSON.stringify(queriedAuthor, undefined, 2));
    console.log("");
    return Book.findById(testBook._id).populate("author author.agent").execAsync();
  }).then(function(queriedBook){
    console.log("book after finding and populating book: ");
    console.log(JSON.stringify(queriedBook, undefined, 2));
    console.log("");
    return Book.findByIdAsync(testBook._id);
  }).then(function(foundBooks){
    console.log("book after finding book:");
    console.log(JSON.stringify(foundBooks, undefined, 2));
    console.log("");
    return foundBooks.populateAsync("author");
  }).then(function(populatedBook){
    console.log("book after populating book: ");
    console.log(JSON.stringify(populatedBook, undefined, 2));
    process.exit();
  })['catch'](function(err){
    console.log(err);
  });
}).call(this);

LiveScript:

mongoose = require("mongoose-bird")(require("mongoose"))
mongoose.connect "mongodb://test:test@localhost:27017/test"

bookSchemaObj   =
  author:
    type: mongoose.Schema.Types.ObjectId
    ref: "Author"
    required: true
  pubYear:
    type: Number

authorSchemaObj =
  name:
    type: String
    required: true
  agent:
    type: mongoose.Schema.Types.ObjectId
    ref: "Agent"
    required: true

agentSchemaObj  =
  name:
    type: String
    required: true

bookSchema   = new mongoose.Schema bookSchemaObj
authorSchema = new mongoose.Schema authorSchemaObj
agentSchema  = new mongoose.Schema agentSchemaObj

Book   = mongoose.model "Book", bookSchema
Author = mongoose.model "Author", authorSchema
Agent  = mongoose.model "Agent", agentSchema

testBookObj   =
  pubYear: 2001

testAuthorObj =
  name: "John P. Doe"

testAgentObj  =
  name: "Alfred O. Thompson"

testAgent  = new Agent testAgentObj
testAuthor = new Author testAuthorObj
testBook   = new Book testBookObj

testAuthor.agent = testAgent._id
testBook.author  = testAuthor._id

testAgent.saveAsync!
  .then ->
    return testAuthor.saveAsync!

  .then ->
    return testBook.saveAsync!

  .then ->
    console.log "book after saving agent, author, and book:"
    console.log JSON.stringify testBook, undefined, 2
    console.log ""
    return Author.findById(testAuthor._id).populate("agent").execAsync!

  .then (queriedAuthor) ->
    console.log "author after finding and populating author"
    console.log JSON.stringify queriedAuthor, undefined, 2
    console.log ""
    return Book.findById(testBook._id).populate("author author.agent")
      .execAsync!

  .then (queriedBook) ->
    console.log "book after finding and populating book: "
    console.log JSON.stringify queriedBook, undefined, 2
    console.log ""
    return Book.findByIdAsync testBook._id

  .then (foundBooks) ->
    console.log "book after finding book:"
    console.log JSON.stringify foundBooks, undefined, 2
    console.log ""
    return foundBooks.populateAsync "author"

  .then (populatedBook) !->
    console.log "book after populating book: "
    console.log JSON.stringify populatedBook, undefined, 2
    process.exit!

  .catch (err) !->
    console.log err

Output:

book after saving agent, author, and book:
{
  "__v": 0,
  "author": "553a52d4cd8d2a4f5a5c4185",
  "pubYear": 2001,
  "_id": "553a52d4cd8d2a4f5a5c4186"
}

author after finding and populating author
{
  "_id": "553a52d4cd8d2a4f5a5c4185",
  "agent": {
    "_id": "553a52d4cd8d2a4f5a5c4184",
    "name": "Alfred O. Thompson",
    "__v": 0
  },
  "name": "John P. Doe",
  "__v": 0
}

book after finding and populating book: 
{
  "_id": "553a52d4cd8d2a4f5a5c4186",
  "author": {
    "_id": "553a52d4cd8d2a4f5a5c4185",
    "name": "John P. Doe",
    "__v": 0
  },
  "pubYear": 2001,
  "__v": 0
}

book after finding book:
{
  "_id": "553a52d4cd8d2a4f5a5c4186",
  "pubYear": 2001,
  "__v": 0
}

book after populating book: 
{
  "_id": "553a52d4cd8d2a4f5a5c4186",
  "pubYear": 2001,
  "__v": 0
}

Upvotes: 1

Views: 615

Answers (1)

Edward Lee
Edward Lee

Reputation: 951

Recursive population of multi-level deep refs can be achieved by calling Model.populate ad infinitum to populate structures that span more than 2 collections.

In case of multi-level deep ref, multiple populate, populate("author author.agent"), doesn't work. You have to populate author first and then populate author.agent on Agent.

If you do not know the agent whose ID you wish to populate, findById and populate the Author, then use a variable with scope outside the promise to store the populated agent ID, then re-add it, then populate with Agent.populate.

var agentId = null;

testAgent.saveAsync().then(function(){
    return testAuthor.saveAsync();
}).then(function(){
    return testBook.saveAsync();
}).then(function(){
    return Author.findById(testAuthor._id).populate("agent").execAsync!
}).then(function(populatedAuthor){
    console.log("book after saving agent, author, and book:");
    console.log(JSON.stringify(testBook, undefined, 2));
    console.log("");
    agentId = populatedAuthor.agent._id;
    return Book.findById(testBook._id).populate("author").execAsync();
}).then(function(partiallyPopulatedBook){
    console.log("author after finding and populating author:");
    console.log(JSON.stringify(partiallyPopulatedBook, undefined, 2));
    console.log("");
    partiallyPopulatedBook.author.agent = agentId;
    return Agent.populate(partiallyPopulatedBook, {path:"author.agent"});
}).then(function(populatedBook){
    console.log("author after finding and populating author and autho.agent:");
    console.log(JSON.stringify(populatedBook, undefined, 2));
    process.exit();
})['catch'](function(err){
    console.log(err);
});

Upvotes: 1

Related Questions