Mongoose zapełnia się po zapisaniu

100

Nie mogę ręcznie lub automatycznie wypełnić pola twórcy nowo zapisanego obiektu ... jedynym sposobem, w jaki mogę znaleźć, jest ponowne wysłanie zapytania o obiekty, które już mam, czego nie chciałbym robić.

To jest konfiguracja:

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

W tym miejscu ciągnę za włosy

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

EDYCJA: najnowsza mangusta naprawiła ten problem i dodała funkcję wypełniania, zobacz nową zaakceptowaną odpowiedź.

Pykler
źródło

Odpowiedzi:

141

Aby to zrobić, powinieneś być w stanie użyć funkcji wypełniania modelu: http://mongoosejs.com/docs/api.html#model_Model.populate W module obsługi zapisywania dla książki, zamiast:

book._creator = user;

zrobiłbyś coś takiego:

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

Prawdopodobnie za późno na odpowiedź, aby ci pomóc, ale ostatnio utknąłem na tym i może się przydać innym.

user1417684
źródło
Byłoby miło, gdyby działało to z atrybutami wirtualnymi. jakcreator.profile
chovy
jeśli użytkownik ma jakieś wirtualne atrybuty, nie są one uwzględniane.
chovy
To zadziałało dla mnie, chociaż miałem pewne problemy, gdy próbowałem ustawić odniesienie na null tuż przed zapisaniem. Po zapisaniu wywołanie Book.populate nieprawidłowo wypełniło pole poprzednią wartością odniesienia i nie mogę zrozumieć, dlaczego. Baza danych pomyślnie zawiera wartość null.
Daniel Flippance,
2
I to nie spowoduje ponownego zapytania do bazy danych ?!
Nepoxx
9
to jest ponowne wysłanie zapytania do bazy danych
bubakazouba
37

Na wypadek, gdyby ktoś nadal tego szukał.

Mongoose 3.6 wprowadził wiele fajnych funkcji do zapełnienia:

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

lub:

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

zobacz więcej na: https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes#population

Ale w ten sposób nadal będziesz pytać o użytkownika.

Mała sztuczka, aby to osiągnąć bez dodatkowych zapytań, to:

book = book.toObject();
book._creator = user;
Eric Saboia
źródło
wykonanie book._creator = user;po save()jest jedyną poprawną odpowiedzią spośród wszystkich aktualnych odpowiedzi, wszystkie inne odpowiedzi wymagają dodatkowego zapytania.
Mr5o1,
25

Rozwiązaniem dla mnie było użycie execPopulate, tak

const t = new MyModel(value)
return t.save().then(t => t.populate('my-path').execPopulate())
François Romain
źródło
3
Wielkie dzięki @Francois, uratowałeś mi życie, próbowałem znaleźć rozwiązanie tego problemu. W końcu to rozumiem.
Rupesh
22

Rozwiązanie, które zwraca obietnicę (bez oddzwonień):

Użyj wypełnienia dokumentu #

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

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

Możliwa implementacja

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

Przeczytaj o populacji dokumentów tutaj .

Govind Rai
źródło
Dobry towar. Dzięki
Joel H
11

Żeby rozwinąć i podać inny przykład, bo to mi pomogło. Może to pomóc tym, którzy chcą odzyskać częściowo zapełnione obiekty po zapisaniu. Metoda jest również nieco inna. Spędził ponad godzinę lub dwie, szukając właściwego sposobu na zrobienie tego.

  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
źródło
10

Pomyślałem, że dodam do tego, aby wyjaśnić rzeczy kompletnym noobom takim jak ja.

Jeśli nie jesteś ostrożny, ogromnie zagmatwane jest to, że istnieją trzy bardzo różne metody wypełniania. Są to metody różnych obiektów (model vs. dokument), pobierają różne dane wejściowe i dają różne wyniki (dokument vs. obietnica).

Oto one dla tych, którzy są zaskoczeni:

Document.prototype.populate ()

Zobacz pełną dokumentację.

Ten pracuje na dokumentach i zwraca dokument. W oryginalnym przykładzie wyglądałoby to tak:

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

Ponieważ działa na dokumentach i zwraca dokument, możesz je połączyć w następujący sposób:

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

Ale nie bądź głupi, jak ja, i spróbuj zrobić to:

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

Pamiętaj: Document.prototype.populate () zwraca dokument, więc to nonsens. Jeśli chcesz mieć obietnicę, potrzebujesz ...

Document.prototype.execPopulate ()

Zobacz pełną dokumentację.

Ten działa na dokumentach, ALE zwraca obietnicę, która rozwiązuje się w dokumencie. Innymi słowy, możesz go używać w ten sposób:

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

Tak lepiej. Wreszcie jest ...

Model.populate ()

Zobacz pełną dokumentację.

Ten działa na modelach i zwraca obietnicę. Dlatego jest używany nieco inaczej:

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

Mam nadzieję, że pomogło to niektórym innym nowo przybyłym.

curzmg
źródło
1

Niestety jest to długotrwały problem z mangustą, który moim zdaniem nie został jeszcze rozwiązany:

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

To, co możesz zrobić, to napisać własny niestandardowy program pobierający / ustawiający (i ustawić wartość real _customer w oddzielnej właściwości). Na przykład:

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

UWAGA: Nie testowałem tego i może działać dziwnie z .populatei podczas ustawiania czystego identyfikatora.

kapryśny
źródło
wydaje się, że nie chcą rozwiązać problemu.
Pykler
1
ten problem został rozwiązany w 3.6
mkoryak
@Pykler naprawdę musisz zmienić zaakceptowaną odpowiedź na najwyżej ocenioną wcześniej, ponieważ ta odpowiedź jest już nieaktualna
Matt Fletcher
1

Mangusta 5.2.7

To działa dla mnie (tylko duży ból głowy!)

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
źródło
0

Prawdopodobnie coś. lubić

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

Byłby to najmilszy i najmniej problematyczny sposób na wykonanie tego zadania (obietnice dotyczące korzystania z Bluebird).

kboom
źródło
0

skończyło się na napisaniu kilku funkcji Promise, które mogą być używane w curry, w których deklarujesz schemat, funkcje query_adapter, data_adapter i wypełniasz ciąg z wyprzedzeniem. Dla łatwiejszej / krótszej implementacji.

Prawdopodobnie nie jest super wydajny, ale pomyślałem, że wykonanie było dość eleganckie.

plik github: curry_Promises.js

Deklaracja

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

wykonanie

Promise.all( items.map( update_or_insert_Item ) )
.catch( console.error )
.then( console.log )
WouldBeNerd
źródło