Мангуст заселяється після збереження


96

Я не можу вручну або автоматично заповнити поле творця на нещодавно збереженому об'єкті ... єдиний спосіб, який я можу знайти, це повторний запит щодо вже наявних у мене об'єктів, які я б ненавидів робити.

Ось таке налаштування:

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

Тут я витягую волосся

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: останній мангуст виправив цю проблему та додав функціональність заповнення, див. Нову прийняту відповідь.

Відповіді:


136

Ви повинні мати можливість використовувати функцію заповнення моделі для цього: http://mongoosejs.com/docs/api.html#model_Model.populate У обробнику збереження для книги, замість:

book._creator = user;

ви зробите щось на зразок:

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

Можливо, занадто пізня відповідь, щоб допомогти вам, але я недавно зупинився на цьому, і це може бути корисно для інших.


Було б непогано, якби це працювало з віртуальними атрибутами. likecreator.profile
chovy

якщо користувач має деякі віртуальні атрибути, вони не включаються.
chovy

Це спрацювало для мене, хоча у мене були деякі проблеми, коли я намагався встановити посилання на null безпосередньо перед тим, як зберегти. Після збереження виклик Book.populate неправильно заповнив поле попереднім посиланням, і я не можу зрозуміти, чому. БД успішно містить нуль.
Даніель Фліппанс,

2
І це не призведе до повторного запиту до бази даних ?!
Nepoxx

9
це повторний запит до бази даних
bubakazouba

36

На випадок, якщо хтось все ще шукає це.

Mongoose 3.6 представив багато цікавих функцій для заповнення:

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

або:

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

докладніше див .: https://github.com/LearnBoost/mongoose/wiki/3.6-Release-Notes#population

Але таким чином ви все одно знову запитаєте користувача.

Маленьким трюком для досягнення цього без зайвих запитів буде:

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

робити book._creator = user;після save()- це єдина правильна відповідь серед усіх поточних відповідей, для всіх інших відповідей потрібен додатковий запит.
Mr5o1

23

Рішенням для мене було користуватися execPopulate, начебто так

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

2
Велике спасибі @Francois, Ти врятував мені життя, я намагався знайти рішення для цього. Нарешті зрозумів це.
Рупеш,

18

Рішення, яке повертає обіцянку (без зворотних викликів):

Використовуйте заповнення # документа

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

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

Можливе впровадження

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

Про кількість документів читайте тут .


Хороший матеріал. Дякую
Джоель Х,

11

Тільки для деталізації та наведення іншого прикладу, як це мені допомогло. Це може допомогти тим, хто хоче отримати частково заселені об'єкти після збереження. Метод також дещо відрізняється. Витратив більше години-двох на пошуки правильного способу зробити це.

  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

Я думав, що додам до цього, щоб пояснити речі для таких повних людей, як я.

Що дуже бентежить, якщо ви не обережні, це те, що існує три дуже різних методи заповнення. Вони є методами різних об'єктів (Модель проти документа), беруть різні вхідні дані і дають різні результати (Документ проти обіцянки).

Ось вони для тих, хто спантеличений:

Document.prototype.populate ()

Див. Повну документацію.

Цей працює над документами та повертає документ. В оригінальному прикладі це буде виглядати так:

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

Оскільки він працює з документами і повертає документ, ви можете зв'язати їх таким чином:

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

Але не будь дурним, як я, і спробуй зробити це:

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

Пам’ятайте: Document.prototype.populate () повертає документ, тож це нонсенс. Якщо ви хочете обіцянку, вам потрібно ...

Document.prototype.execPopulate ()

Див. Повну документацію.

Цей працює з документами, АЛЕ він повертає обіцянку, яка вирішує документ. Іншими словами, ви можете використовувати його так:

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

Так краще. Нарешті, є ...

Model.populate ()

Див. Повну документацію.

Цей працює на моделях і повертає обіцянку. Тому він використовується дещо інакше:

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

Сподіваюся, це допомогло іншим новачкам.


1

На жаль, це давня проблема з мангустом, яку я вважаю ще не вирішеною:

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

Що ви можете зробити, це написати власний власний геттер / сеттер (і встановити реальний _customer в окремій властивості) для цього. Наприклад:

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

ПРИМІТКА: Я не тестував його, і це може дивно працювати з .populateі при встановленні чистого ідентифікатора.


здається, вони не прагнуть вирішити проблему.
Пиклер

1
ця проблема виправлена ​​в 3.6
mkoryak

@Pykler, вам дійсно потрібно змінити прийняту відповідь на найвищу оцінку раніше, оскільки ця відповідь вже недійсна
Метт Флетчер

1

Мангуст 5.2.7

Це працює для мене (просто багато головного болю!)

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

Можливо sth. подібно до

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

Це був би найкращий і найменш проблематичний спосіб зробити цю роботу (використання обіцянок Bluebird).


0

закінчилося написанням деяких функцій Promise з можливістю каррі, де ви заздалегідь оголошуєте свою схему, query_adapter, data_adapter функції та заповнюєте рядок. Для коротшого / простішого впровадження простіше.

Це, мабуть, не надто ефективно, але я думав, що біт виконання був досить елегантним.

файл github: curry_Promises.js

декларація

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

виконання

Promise.all( items.map( update_or_insert_Item ) )
.catch( console.error )
.then( console.log )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.