Mongoose povoa após salvar

96

Não consigo preencher manualmente ou automaticamente o campo do criador em um objeto recém-salvo ... a única maneira que posso encontrar é consultar novamente os objetos que já tenho e que odiaria fazer.

Esta é a configuração:

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

var bookSchema = new mongoose.Schema({
  _creator: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  description: String,
});
var Book = db.model('Book', bookSchema);

É aqui que estou puxando meu cabelo

var user = new User();
user.save(function(err) {
    var book = new Book({
        _creator: user,
    });
    book.save(function(err){
        console.log(book._creator); // is just an object id
        book._creator = user; // still only attaches the object id due to Mongoose magic
        console.log(book._creator); // Again: is just an object id
        // I really want book._creator to be a user without having to go back to the db ... any suggestions?
    });
});

EDITAR: o mangusto mais recente corrigiu esse problema e adicionou a funcionalidade de preenchimento, consulte a nova resposta aceita.

Pykler
fonte

Respostas:

136

Você deve ser capaz de usar a função populate do modelo para fazer isso: http://mongoosejs.com/docs/api.html#model_Model.populate No manipulador de salvamento do livro, em vez de:

book._creator = user;

você faria algo como:

Book.populate(book, {path:"_creator"}, function(err, book) { ... });

Provavelmente uma resposta tarde demais para ajudá-lo, mas eu estava preso nisso recentemente e pode ser útil para outras pessoas.

user1417684
fonte
Seria bom se isso funcionasse com atributos virtuais. likecreator.profile
chovy
se o usuário tiver alguns atributos virtuais, eles não serão incluídos.
chovy
Isso funcionou para mim, embora eu tenha tido alguns problemas quando tentei definir a referência como null antes de salvar. Depois de salvar, chamar Book.populate preencheu incorretamente o campo com o valor de referência anterior e não consigo descobrir o porquê. O banco de dados contém o nulo com sucesso.
Daniel Flippance,
2
E isso não vai consultar novamente o banco de dados ?!
Nepoxx
9
isto é uma nova consulta ao banco de dados
bubakazouba
36

Caso alguém ainda esteja procurando por isso.

O Mongoose 3.6 introduziu muitos recursos interessantes para preencher:

book.populate('_creator', function(err) {
 console.log(book._creator);
});

ou:

Book.populate(book, '_creator', function(err) {
 console.log(book._creator);
});

Veja mais em: https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes#population

Mas, dessa forma, você ainda consultaria o usuário novamente.

Um pequeno truque para realizá-lo sem consultas extras seria:

book = book.toObject();
book._creator = user;
Eric Saboia
fonte
fazer book._creator = user;depois de save()é a única resposta correta entre todas as respostas atuais; todas as outras respostas exigem uma consulta adicional.
Mr5o1
23

A solução para mim foi usar execPopulate, assim

const t = new MyModel(value)
return t.save().then(t => t.populate('my-path').execPopulate())
François Romain
fonte
2
Muito obrigado @Francois, Você salvou minha vida, eu estava tentando encontrar uma solução para isso. Finalmente entendi.
Rupesh
18

Solução que retorna uma promessa (sem retornos de chamada):

Use Documento # preencher

book.populate('creator').execPopulate();

// summary
doc.populate(options);               // not executed
doc.populate(options).execPopulate() // executed, returns promise

Possível Implementação

var populatedDoc = doc.populate(options).execPopulate();
var populatedDoc.then(doc => {
   ... 
});

Leia sobre a população de documentos aqui .

Govind Rai
fonte
Coisa boa. Obrigado
Joel H
11

Só para elaborar e dar outro exemplo, pois me ajudou. Isso pode ajudar aqueles que desejam recuperar objetos parcialmente preenchidos após salvar. O método também é um pouco diferente. Gastei mais de uma ou duas horas procurando a maneira correta de fazer isso.

  post.save(function(err) {
    if (err) {
      return res.json(500, {
        error: 'Cannot save the post'
      });
    }
    post.populate('group', 'name').populate({
      path: 'wallUser',
      select: 'name picture'
    }, function(err, doc) {
      res.json(doc);
    });
  });
Pratik Bothra
fonte
9

Pensei em acrescentar a isso para esclarecer as coisas para novatos completos como eu.

O que é extremamente confuso se você não tomar cuidado é que existem três métodos de preenchimento muito diferentes. Eles são métodos de objetos diferentes (Modelo vs. Documento), recebem diferentes entradas e fornecem diferentes saídas (Documento vs. Promessa).

Aqui estão eles para aqueles que estão perplexos:

Document.prototype.populate ()

Veja a documentação completa.

Este trabalha em documentos e retorna um documento. No exemplo original, seria assim:

book.save(function(err, book) {
    book.populate('_creator', function(err, book) {
        // Do something
    })
});

Como ele funciona em documentos e retorna um documento, você pode encadea-los assim:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */', function(err, book) {
        // Do something
    })
});

Mas não seja bobo, como eu, e tente fazer isso:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */')
    .then(function(book) {
        // Do something
    })
});

Lembre-se: Document.prototype.populate () retorna um documento, então isso é um absurdo. Se você quer uma promessa, você precisa ...

Document.prototype.execPopulate ()

Veja a documentação completa.

Este trabalha em documentos MAS retorna uma promessa que resolve o documento. Em outras palavras, você pode usá-lo assim:

book.save(function(err, book) {
    book
    .populate('_creator')
    .populate('/* Some other ObjectID field */')
    .execPopulate()
    .then(function(book) {
        // Do something
    })
});

Isso é melhor. Finalmente, há ...

Model.populate ()

Veja a documentação completa.

Este funciona em modelos e retorna uma promessa. Portanto, é usado de maneira um pouco diferente:

book.save(function(err, book) {
    Book // Book not book
    .populate(book, { path: '_creator'})
    .then(function(book) {
        // Do something
    })
});

Espero que tenha ajudado alguns outros recém-chegados.

curzmg
fonte
1

Infelizmente, este é um problema de longa data com o mangusto que acredito ainda não foi resolvido:

https://github.com/LearnBoost/mongoose/issues/570

O que você pode fazer é escrever seu próprio getter / setter personalizado (e definir real _customer em uma propriedade separada) para isso. Por exemplo:

var get_creator = function(val) {
    if (this.hasOwnProperty( "__creator" )) {
        return this.__creator;
    }
    return val;
};
var set_creator = function(val) {
    this.__creator = val;
    return val;
};
var bookSchema = new mongoose.Schema({
  _creator: {
     type: mongoose.Schema.Types.ObjectId,
     ref: 'User',
     get: get_creator,
     set: set_creator
  },
  description: String,
});

NOTA: Eu não testei e pode funcionar estranhamente com .populatee ao definir o id puro.

esquisito
fonte
parece que eles não estão tentando resolver o problema.
Pykler
1
este problema foi corrigido em 3.6
mkoryak
@Pykler, você realmente precisa mudar a resposta aceita para a de melhor avaliação anterior, pois esta resposta não é mais válida
Matt Fletcher
1

Mongoose 5.2.7

Isso funciona para mim (apenas muita dor de cabeça!)

exports.create = (req, res, next) => {
  const author = req.userData;
  const postInfo = new Post({
    author,
    content: req.body.content,
    isDraft: req.body.isDraft,
    status: req.body.status,
    title: req.body.title
  });
  postInfo.populate('author', '_id email role display_name').execPopulate();
  postInfo.save()
    .then(post => {
      res.status(200).json(post);
    }).catch(error => {
      res.status(500).json(error);
    });
};
Whisher
fonte
0

Provavelmente sth. gostar

Book.createAsync(bookToSave).then((savedBook) => savedBook.populateAsync("creator"));

Seria a maneira mais agradável e menos problemática de fazer isso funcionar (usando promessas do Bluebird).

kboom
fonte
0

acabou escrevendo algumas funções Promise habilitadas para curry, onde você declara seu schema, query_adapter, data_adapter funções e preenche string com antecedência. Para uma implementação mais curta / simples, mais fácil.

Provavelmente não é muito eficiente, mas achei a parte de execução bem elegante.

arquivo github: curry_Promises.js

declaração

const update_or_insert_Item = mDB.update_or_insert({
    schema : model.Item,
    fn_query_adapter : ({ no })=>{return { no }},
    fn_update_adapter : SQL_to_MDB.item,
    populate : "headgroup"
    // fn_err : (e)=>{return e},
    // fn_res : (o)=>{return o}
})

execução

Promise.all( items.map( update_or_insert_Item ) )
.catch( console.error )
.then( console.log )
WouldBeNerd
fonte