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ź.

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.


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;

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

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 .


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

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.


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.


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

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


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 )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.