Les mangoustes peuplent après l'enregistrement


96

Je ne peux pas remplir manuellement ou automatiquement le champ créateur sur un objet nouvellement enregistré ... le seul moyen que je peux trouver est de ré-interroger les objets que j'ai déjà, ce que je détesterais faire.

Voici la configuration:

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);

C'est là que je me tire les cheveux

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?
    });
});

EDIT: la dernière mangouste a corrigé ce problème et ajouté une fonctionnalité de remplissage, voir la nouvelle réponse acceptée.

Réponses:


136

Vous devriez pouvoir utiliser la fonction populate du modèle pour ce faire: http://mongoosejs.com/docs/api.html#model_Model.populate Dans le gestionnaire de sauvegarde pour book, au lieu de:

book._creator = user;

vous feriez quelque chose comme:

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

Probablement une réponse trop tardive pour vous aider, mais je suis resté coincé là-dessus récemment, et cela pourrait être utile pour d'autres.


Ce serait bien si cela fonctionnait avec des attributs virtuels. likecreator.profile
chovy

si l'utilisateur a des attributs virtuels, ils ne sont pas inclus.
chovy

Cela a fonctionné pour moi même si j'ai eu quelques problèmes lorsque j'ai essayé de définir la référence sur null juste avant d'enregistrer. Après l'enregistrement, l'appel de Book.populate a incorrectement rempli le champ avec la valeur de référence précédente et je ne peux pas comprendre pourquoi. Le DB contient avec succès le null.
Daniel Flippance

2
Et cela ne réinterrogera pas la base de données?!
Nepoxx

9
ceci est en train de
réinterroger

36

Au cas où quelqu'un chercherait encore cela.

Mongoose 3.6 a introduit de nombreuses fonctionnalités intéressantes à remplir:

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

ou:

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

pour en savoir plus: https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes#population

Mais de cette façon, vous interrogeriez à nouveau l'utilisateur.

Une petite astuce pour y parvenir sans requêtes supplémentaires serait:

book = book.toObject();
book._creator = user;

faire book._creator = user;après le save()est la seule réponse correcte parmi toutes les réponses actuelles, toutes les autres réponses nécessitent une requête supplémentaire.
Mr5o1

23

La solution pour moi était d'utiliser execPopulate, comme ça

const t = new MyModel(value)
return t.save().then(t => t.populate('my-path').execPopulate())

2
Merci beaucoup @Francois, Tu m'as sauvé la vie, j'essayais de trouver une solution pour ça. Finalement, j'ai ça.
Rupesh

18

Solution qui renvoie une promesse (pas de callback):

Utiliser le document # populate

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

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

Implémentation possible

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

En savoir plus sur la population de documents ici .


Bon produit. Merci
Joel H

11

Juste pour élaborer et donner un autre exemple, car cela m'a aidé. Cela peut aider ceux qui souhaitent récupérer des objets partiellement remplis après l'enregistrement. La méthode est également légèrement différente. J'ai passé plus d'une heure ou deux à chercher la bonne façon de le faire.

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

9

J'ai pensé ajouter à cela pour clarifier les choses pour des noobs complets comme moi.

Ce qui est extrêmement déroutant si vous ne faites pas attention, c'est qu'il existe trois méthodes de peuplement très différentes. Ce sont des méthodes d'objets différents (modèle vs document), prennent différentes entrées et donnent des sorties différentes (document vs promesse).

Les voici pour ceux qui sont déconcertés:

Document.prototype.populate ()

Voir la documentation complète.

Celui-ci fonctionne sur des documents et renvoie un document. Dans l'exemple d'origine, cela ressemblerait à ceci:

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

Comme cela fonctionne sur les documents et renvoie un document, vous pouvez les enchaîner comme suit:

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

Mais ne soyez pas stupide, comme moi, et essayez de faire ceci:

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

Rappelez-vous: Document.prototype.populate () renvoie un document, c'est donc un non-sens. Si vous voulez une promesse, vous avez besoin de ...

Document.prototype.execPopulate ()

Voir la documentation complète.

Celui-ci fonctionne sur les documents MAIS il renvoie une promesse qui résout le document. En d'autres termes, vous pouvez l'utiliser comme ceci:

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

C'est mieux. Enfin, il y a ...

Model.populate ()

Voir la documentation complète.

Celui-ci fonctionne sur des modèles et renvoie une promesse. Il est donc utilisé un peu différemment:

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

J'espère que cela a aidé d'autres nouveaux arrivants.


1

Malheureusement, c'est un problème de longue date avec la mangouste qui, je crois, n'est pas encore résolu:

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

Ce que vous pouvez faire est d'écrire votre propre getter / setter personnalisé (et définir real _customer dans une propriété distincte) pour cela. Par exemple:

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,
});

REMARQUE: je ne l'ai pas testé et cela pourrait fonctionner étrangement avec .populateet lors de la configuration de l'id pur.


il semble qu'ils ne cherchent pas à résoudre le problème.
Pykler

1
ce problème est résolu dans la version 3.6
mkoryak

@Pykler, vous devez vraiment changer la réponse acceptée par la réponse la mieux notée auparavant, car cette réponse n'est plus valide
Matt Fletcher

1

Mangouste 5.2.7

Cela fonctionne pour moi (juste beaucoup de maux de tête!)

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

0

Probablement qch. comme

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

Ce serait le moyen le plus agréable et le moins problématique de faire ce travail (en utilisant les promesses de Bluebird).


0

a fini par écrire des fonctions Promise curry-able où vous déclarez votre schéma, query_adapter, data_adapter fonctions et remplissez la chaîne à l'avance. Pour une implémentation plus courte / plus simple, plus facile.

Ce n'est probablement pas super efficace, mais j'ai trouvé que le bit d'exécution était assez élégant.

fichier github: curry_Promises.js

déclartion

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}
})

exécution

Promise.all( items.map( update_or_insert_Item ) )
.catch( console.error )
.then( console.log )
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.