Сохранение и поиск документов Mongoose с вложенными схемами с ссылками и обещаниями

У меня достаточно простой вопрос. Я пытаюсь сохранить документ, схема которого включает вложенную ссылку на схему, и эта ссылка на схему включает другую ссылку на схему. Однако когда я иду, чтобы получить этот документ, он не включает (обязательное) вложенное поле, если я не заполню его в том же запросе. Однако даже когда я заполняю запрос, второй вложенный документ не заполняется. Я неправильно понимаю что-то фундаментальное в том, как ссылки работают в мангусте?

Пример кода JavaScript и LiveScript и вывод ниже.


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

Выход:

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
}

person Californian    schedule 23.04.2015    source источник


Ответы (1)


Рекурсивное заполнение многоуровневых глубоких ссылок может быть достигнуто путем вызова Model.populate до бесконечности для заполнения структур, охватывающих более 2 коллекций.

В случае многоуровневой глубокой ссылки множественное заполнение populate("author author.agent") не работает. Вы должны сначала заполнить author, а затем заполнить author.agent на Agent.

Если вы не знаете агента, чей идентификатор вы хотите заполнить, найдитеById и заполните Author, затем используйте переменную с областью действия вне обещания для хранения заполненного идентификатора агента, затем повторно добавьте его, а затем заполните с помощью 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);
});
person Edward Lee    schedule 23.04.2015
comment
Любую обычную функцию мангуста можно использовать с промисами, добавив Async к имени. Я внес изменения и обновил вопрос, чтобы показать реальную проблему, с которой я столкнулся. - person Californian; 24.04.2015
comment
У вас проблема с многоуровневой глубокой ссылкой. Надеюсь, мой отредактированный ответ поможет вам. - person Edward Lee; 24.04.2015
comment
Это кажется очень близким, так что уже большое спасибо. Однако, когда я запускаю это, я получаю, что undefined не является функцией. Я также пробовал Author.populate, но это тоже не сработало. - person Californian; 24.04.2015
comment
Model.populate возвращает Promise, поэтому execAsync следует удалить после Agent.populate. Пожалуйста, попробуйте еще раз с моим отредактированным кодом. - person Edward Lee; 24.04.2015
comment
Это все очень халтурно и уродливо, и я не уверен, виноват ли в этом Мангуст, или мангуст-птица, или синяя птица, но я, наконец, понял это. Ваш код почти работает, единственное незначительное (и чрезвычайно хакерское) изменение заключается в том, что мне нужно повторно добавить идентификатор агента перед заполнением. Однако это ужасно, поэтому, если вы можете посоветовать мне, где я должен открыть отчет об ошибке или какой источник репо посмотреть, я был бы очень признателен. Я отредактирую ваш ответ и добавлю соответствующие изменения кода. - person Californian; 24.04.2015
comment
Вы можете регистрировать выходные данные и ошибки. node app.js > app_log.log 2> app_err.log - person Edward Lee; 24.04.2015