Sails.js заполняют вложенные ассоциации

У меня вопрос о ассоциациях в Sails.js версии 0.10-rc5. Я создаю приложение, в котором несколько моделей связаны друг с другом, и я пришел к точке, где мне нужно как-то развить ассоциации.

Есть три части:

Сначала есть что-то вроде сообщения в блоге, которое написано пользователем. В сообщении в блоге я хочу показать информацию связанного пользователя, такую ​​как имя пользователя. Теперь все отлично работает. До следующего шага: я пытаюсь показать комментарии, связанные с этим сообщением.

Комментарии представляют собой отдельную модель, называемую комментарием. Каждый из них также имеет автора (пользователя), связанного с ним. Я могу легко показать список комментариев, хотя, когда я хочу отображать информацию пользователя, связанную с комментарием, я не могу понять, как заполнить комментарий информацией пользователя.

В моем controllerе я пытаюсь сделать что-то вроде этого:

Post .findOne(req.param('id')) .populate('user') .populate('comments') // I want to populate this comment with .populate('user') or something .exec(function(err, post) { // Handle errors & render view etc. }); 

В действии «show» моего сообщения я пытаюсь получить такую ​​информацию (упрощенную):

 

Однако comment.user.name будет неопределенным. Если я попытаюсь просто получить доступ к свойству user, например comment.user, он покажет его идентификатор. Который говорит мне, что это не автоматически заполняет информацию пользователя комментарию, когда я связываю комментарий с другой моделью.

Любые идеалы, чтобы решить это правильно :)?

Заранее спасибо!

PS

Для уточнения, вот как я в основном создал ассоциации в разных моделях:

 // User.js posts: { collection: 'post' }, hours: { collection: 'hour' }, comments: { collection: 'comment' } // Post.js user: { model: 'user' }, comments: { collection: 'comment', via: 'post' } // Comment.js user: { model: 'user' }, post: { model: 'post' } 

    Или вы можете использовать встроенную функцию Blue Bird Promise, чтобы сделать это. (Работа на [email protected].5)

    См. Следующие коды:

     var _ = require('lodash'); ... Post .findOne(req.param('id')) .populate('user') .populate('comments') .then(function(post) { var commentUsers = User.find({ id: _.pluck(post.comments, 'user') //_.pluck: Retrieves the value of a 'user' property from all elements in the post.comments collection. }) .then(function(commentUsers) { return commentUsers; }); return [post, commentUsers]; }) .spread(function(post, commentUsers) { commentUsers = _.indexBy(commentUsers, 'id'); //_.indexBy: Creates an object composed of keys generated from the results of running each element of the collection through the given callback. The corresponding value of each key is the last element responsible for generating the key post.comments = _.map(post.comments, function(comment) { comment.user = commentUsers[comment.user]; return comment; }); res.json(post); }) .catch(function(err) { return res.serverError(err); }); 

    Некоторое объяснение:

    1. Я использую Lo-Dash для работы с массивами. Более подробную информацию см. В Официальном Доке
    2. Обратите внимание на возвращаемые значения внутри первой «затем» функции, эти объекты «[post, commentsUsers]» внутри массива также являются «обетованными» объектами. Это означает, что они не содержали данные о значении при их первом запуске, пока не получили значение. Таким образом, функция «распространения» будет ожидать, когда значение атрибута придет и продолжит выполнение остальных вещей.

    На данный момент нет встроенного способа заполнения вложенных ассоциаций. Лучше всего использовать async для создания сопоставления:

     async.auto({ // First get the post post: function(cb) { Post .findOne(req.param('id')) .populate('user') .populate('comments') .exec(cb); }, // Then all of the comment users, using an "in" query by // setting "id" criteria to an array of user IDs commentUsers: ['post', function(cb, results) { User.find({id: _.pluck(results.post.comments, 'user')}).exec(cb); }], // Map the comment users to their comments map: ['commentUsers', function(cb, results) { // Index comment users by ID var commentUsers = _.indexBy(results.commentUsers, 'id'); // Get a plain object version of post & comments var post = results.post.toObject(); // Map users onto comments post.comments = post.comments.map(function(comment) { comment.user = commentUsers[comment.user]; return comment; }); return cb(null, post); }] }, // After all the async magic is finished, return the mapped result // (or an error if any occurred during the async block) function finish(err, results) { if (err) {return res.serverError(err);} return res.json(results.map); } ); 

    Это не так хорошо, как nested наseleniumие (которое работает, но, вероятно, не для v0.10), но на яркой стороне это на самом деле довольно эффективно.

    Стоит сказать, что есть запрос на растяжение для добавления вложенного наseleniumия: https://github.com/balderdashy/waterline/pull/1052

    Запрос Pull не объединяется в данный момент, но вы можете использовать его, устанавливая один непосредственно с

     npm i Atlantis-Software/waterline#deepPopulate 

    С его помощью вы можете сделать что-то вроде .populate('user.comments ...)' .

      sails v0.11 doesn't support _.pluck and _.indexBy use sails.util.pluck and sails.util.indexBy instead. async.auto({ // First get the post post: function(cb) { Post .findOne(req.param('id')) .populate('user') .populate('comments') .exec(cb); }, // Then all of the comment users, using an "in" query by // setting "id" criteria to an array of user IDs commentUsers: ['post', function(cb, results) { User.find({id:sails.util.pluck(results.post.comments, 'user')}).exec(cb); }], // Map the comment users to their comments map: ['commentUsers', function(cb, results) { // Index comment users by ID var commentUsers = sails.util.indexBy(results.commentUsers, 'id'); // Get a plain object version of post & comments var post = results.post.toObject(); // Map users onto comments post.comments = post.comments.map(function(comment) { comment.user = commentUsers[comment.user]; return comment; }); return cb(null, post); }] }, // After all the async magic is finished, return the mapped result // (or an error if any occurred during the async block) function finish(err, results) { if (err) {return res.serverError(err);} return res.json(results.map); } ); 

    Я создал модуль NPM для этого типа nested-pop . Вы можете найти его по ссылке ниже.

    https://www.npmjs.com/package/nested-pop

    Используйте его следующим образом.

     var nestedPop = require('nested-pop'); User.find() .populate('dogs') .then(function(users) { return nestedPop(users, { dogs: [ 'breed' ] }).then(function(users) { return users }).catch(function(err) { throw err; }); }).catch(function(err) { throw err; ); 

    Вы можете использовать асинхронную библиотеку, которая очень чиста и проста для понимания. Для каждого комментария, связанного с сообщением, вы можете заполнить много полей, как вы хотите, выделенными задачами, выполнить их параллельно и получить результаты, когда все задачи будут выполнены. Наконец, вы должны только вернуть окончательный результат.

     Post .findOne(req.param('id')) .populate('user') .populate('comments') // I want to populate this comment with .populate('user') or something .exec(function (err, post) { // populate each post in parallel async.each(post.comments, function (comment, callback) { // you can populate many elements or only one... var populateTasks = { user: function (cb) { User.findOne({ id: comment.user }) .exec(function (err, result) { cb(err, result); }); } } async.parallel(populateTasks, function (err, resultSet) { if (err) { return next(err); } post.comments = resultSet.user; // finish callback(); }); }, function (err) {// final callback if (err) { return next(err); } return res.json(post); }); }); 

    Начиная с sailsjs 1.0 запрос на запрос «глубокого заполнения» остается открытым, но следующее решение функции async выглядит достаточно элегантно:

     var post = await Post .findOne({ id: req.param('id') }) .populate('user') .populate('comments'); if (post && post.comments.length > 0) { post.comments = await Comment .find({ id: post.comments.map((comment) => comment.id) }) .populate('user'); } 
    Давайте будем гением компьютера.