Как я справляюсь с вложенными запросами мангуста и асинхронными проблемами в моих отношениях "один ко многим"?
Я пытаюсь запросить все сообщения из базы данных, а затем захватить все комментарии, принадлежащие каждому сообщению, и отправить все это обратно на передний край для отображения. Моя стратегия до сих пор использует вложенные запросы мангустов (см. примеры псевдокода и фактического кода ниже), и я получаю некоторые неожиданные результаты из-за проблем с асинхронностью.
Может ли кто-нибудь сказать мне, где я ошибаюсь, или есть ли лучший способ сделать то, что я пытаюсь сделать:Мой Схемы:
У меня есть три Schemas
в Мангусте:
- UserSchema (
User
) - PostSchema (
Post
) - CommentSchema (
PostComment
)
Я включил сюда только CommentSchema
, чтобы упростить свой вопрос:
var CommentSchema = new mongoose.Schema (
{
message: {
type: String,
minlength: [2, 'Your comment must be at least 2 characters.'],
maxlength: [2000, 'Your comment must be less than 2000 characters.'],
required: [true, 'You cannot submit an empty comment.'],
trim: true,
}, // end message field
userID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
username: {
type: String,
},
postID: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
},
},
{
timestamps: true,
}
);
При создании нового комментария, _id
сообщения записывается в поле .postID
комментария.
Моя Псевдокодовая Стратегия:
// query for all posts using a mongoose promise
// run a `for loop` through the array returned posts
// query my comments collection for any comments pertaining to post[i]
// attach comments returned to the post[i]
// push post[i] into a new array (now with comments attached)
// check if on last run through array
// res.json the new array back to the front end
// On front end just iterate through each post and its contained comments.
Однако, когда я пытаюсь использовать эту стратегию, я получаю некоторые асинхронные проблемы с моим вторым Запрос Мангуста в цикле for.
Мой Фактический Пример Кода:
Post.find({}) // first query
.then(function(allPosts) {
for (var i = 0; i < allPosts.length; i++) {
_post = allPosts[i];
console.log(_post, i);
PostComment.find({postID: _post._id}) // **nested query
.then(function(comments) {
console.log({
post: _post, // this value will not match the console log above (outside of the query)
index_value: i, // this value too will be out of match with the console log above
comments: comments,
});
// attach new comment to allPosts[i]
// push post with comment attached to new array
// check if on last iteration, if so res.json new array
})
.catch(function(err) {
console.log(err);
})
}
.catch(function(err) {
console.log(err);
}
Проблема из приведенного выше примера кода:
В приведенном выше примере во втором запросе **nested query
, значение i
и _post
не синхронизированы к моменту возврата данных из обещания мангуста (.then
). Цикл for развивается быстрее, чем возвращаются данные. Таким образом, если я попытаюсь прикрепить какие-либо комментарии к родительскому объекту post (_post
), переменная уже не синхронизирована с прогрессия цикла for (_post
теперь становится следующей позицией в массиве). Я озадачен тем, как исправить это и получить все мои комментарии от каждого поста, и связать это вместе для переднего плана. Я просто запутался в этом вопросе.
Желаемое Поведение:
Я хочу, чтобы заполненный список всех моих сообщений с комментариями, прикрепленными к каждому сообщению, облегчал их повторение на переднем конце. Таким образом, на передней панели все сообщения отображаются с соответствующими комментариями ниже их.
Вывод:
Что я делаю не так? Как я могу перебрать все мои сообщения, и захватить все комментарии для каждого, и сделать его аккуратным для фронтального отображения в Angular? Является ли мой подход неправильным или слишком "дорогостоящим" в моих запросах? Есть ли лучший способ добиться желаемого поведения?
Любое понимание или помощь выше ценится! Я искал вокруг в надежде увидеть другой вопрос, подобный этому, и уже некоторое время бьюсь головой об эту проблему=)
1 ответ:
Я придумал, как это сделать. Мне пришлось внести некоторые изменения в свои схемы. Поскольку Mongo не допускает объединения, мы можем вместо этого использовать метод
Чтобы сократить это объяснение, я хотел показать обновленные схемы, а затем показать, как я смог заполнить только один уровень. В нижней части этого сообщения приведен еще один пример, показывающий, как заполнить несколько уровни.populate()
Мангуста (http://mongoosejs.com/docs/populate.html).UserSchema:
// Setup dependencies: var mongoose = require('mongoose'), Schema = mongoose.Schema; // Setup a schema: var UserSchema = new Schema ( { username: { type: String, minlength: [2, 'Username must be at least 2 characters.'], maxlength: [20, 'Username must be less than 20 characters.'], required: [true, 'Your username cannot be blank.'], trim: true, unique: true, // username must be unique dropDups: true, }, // end username field }, { timestamps: true, } ); // Instantiate our model and export it: module.exports = mongoose.model('User', UserSchema);
PostSchema:
// Setup dependencies: var mongoose = require('mongoose'), Schema = mongoose.Schema; // Setup a schema: var PostSchema = new Schema ( { message: { type: String, minlength: [2, 'Your post must be at least 2 characters.'], maxlength: [2000, 'Your post must be less than 2000 characters.'], required: [true, 'You cannot submit an empty post.'], trim: true, }, // end message field user: { type: Schema.Types.ObjectId, ref: 'User' }, comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }], }, { timestamps: true, } ); // updates userID based upon current session login info: PostSchema.methods.updateUserID = function(id) { this.user = id; this.save(); return true; }; // adds comment to post's comments array: PostSchema.methods.addComment = function(commentID) { this.comments.push(commentID); this.save(); return true; }; // Instantiate our model and export it: module.exports = mongoose.model('Post', PostSchema);
CommentSchema:
// Setup dependencies: var mongoose = require('mongoose'), Schema = mongoose.Schema; // Setup a schema: var CommentSchema = new Schema ( { message: { type: String, minlength: [2, 'Your comment must be at least 2 characters.'], maxlength: [2000, 'Your comment must be less than 2000 characters.'], required: [true, 'You cannot submit an empty comment.'], trim: true, }, // end message field user: { type: Schema.Types.ObjectId, ref: 'User' }, _post: { type: Schema.Types.ObjectId, ref: 'Post' }, }, { timestamps: true, } ); // Assigns user ID to comment when called (uses session info): CommentSchema.methods.updateUserID = function(id) { this.user = id; this.save(); return true; }; // Assigns post ID to comment when called: CommentSchema.methods.updatePostID = function(id) { this._post = id; this.save(); return true; }; // Instantiate our model and export it: module.exports = mongoose.model('Comment', CommentSchema);
Решение: используйте метод заполнения:
Используя метод
populate()
(http://mongoosejs.com/docs/populate.html ) мы можем начать с нашей Постмодели и заполнить полеcomments
.Используя методы экземпляра, определенные в PostSchema, мы помещаем идентификаторы комментариев в массив
Post.comments
, который методpopulate()
ниже захватывает все объекты комментариев и заменяет идентификаторы с реальными объектами комментариев.var User = require('mongoose').model('User'); var Post = require('mongoose').model('Post'); var PostComment = require('mongoose').model('Comment'); Post.find({}) .populate('comments') // populates comments objects based on ids in `comments` .exec() .then(function(commentsAndPosts) { console.log(commentsAndPosts); return res.json(commentsAndPosts); }) .catch(function(err) { console.log(err); return res.json(err); })
Ссылка, предоставленная на документацию Мангуста, имеет хороший чистый пример.
Теперь, на переднем конце, каждый объект post имеет массив комментариев внутри, и все объекты комментариев были заполнены! Мило!
Обзор:
Мне удалось сохранить массив идентификаторов комментариев внутри каждого объекта Post (когда создается новый комментарий,
comment._id
помещается в массивPost.comments
). Используяpopulate()
, мы можем запросить коллекцию комментариев и захватите все объекты комментариев, связанные с соответствующим идентификатором. Это здорово, так как после того, как наш метод заполнения завершен, мы можем отправить назад весь наш массив всех сообщений и комментариев как один объект JSON и повторить их на переднем конце.Заселение на нескольких уровнях:
Предположим, в приведенных выше сценариях я также хотел получить имя пользователя для авторов сообщений и комментариев (Примечание: каждый объект комментариев и сообщений имеет полеuser
, в котором хранитсяUser._id
.Мы можем использовать заполните, вложив некоторые параметры, чтобы достичь нескольких уровней следующим образом. Это будет предоставлять те же данные, что и в примере выше (все сообщения и все комментарии), но включает имена пользователей для комментариев и сообщений на основе сохраненного идентификатора пользователя:
Post.find({}) // finds all posts .populate('user') // populates the user (based on ID) and returns user object (entire user object) .populate({ path: 'comments', // populates all comments based on comment ID's populate: { path: 'user' } // populates all 'user" fields for all comments (again, based on user ID) }) .exec() .then(function(commentsAndPostsWithUsers) { console.log(commentsAndPostsWithUsers); return res.json(commentsAndPostsWithUsers); }) .catch(function(err) { console.log(err); })
В приведенном выше примере мы начинаем с захвата всех сообщений, а затем захватываем все объекты пользователя для каждого сообщения,а затем все комментарии вместе с каждым объектом пользователя для каждого комментария и связываем все это!
Перебираем дальше Передний конец с угловым:
Мы можем перебирать наши возвращенные сообщения, используя
ng-repeat
**:<!-- Repeating Posts --> <div ng-repeat="post in commentsAndPostsWithUsers > <h3 ng-bind="post.user.username"></h3> <h2 ng-bind="post.createdAt"></h2> <p ng-bind="post.message"></p> <!-- Repeating Comments --> <div ng-repeat="comment in post.comments"> <h4 ng-bind="comment.user.username"></h4> <h5 ng-bind="comment.createdAt"></h5> <p ng-bind="comment.message"></p> </div> </div>
** угловой контроллер не показан в приведенном выше примере кода.
Мы можем получить доступ к имени пользователя для сообщений или комментариев через,
post.user.username
илиpost.user.username
, соответственно, поскольку весь объект пользователя был прикреплен (таким образом, почему мы должны перейти вниз, чтобы получить имя пользователя). Затем мы можем использовать другойng-repeat
для перебораpost.comments
, показывая все комментарии. Желаю всего наилучшего и надеюсь, что это поможет кто-нибудь, избегайте путаницы, которую я сделал!