Sails.js populate nested associations

I have a question regarding associations in Sails.js version 0.10-rc5. I am creating an application in which several models are connected to each other, and I have come to the point where I need to somehow associate associations.

There are three parts:

At first there is something like a blog post written by a user. In a blog post, I want to show relevant user information, such as username. Now everything works fine. Until the next step: I'm trying to show comments related to this post.

Comments are a separate model called Comment. Each of them also has an author (user) associated with it. I can easily show the list of comments, although when I want to display the user information associated with the comment, I cannot figure out how to populate the comment with user information.

In my controller, I am trying to do something like this:

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

In the "show" action of my message, I try to get information similar to this (simplified):

 <ul> <%- _.each(post.comments, function(comment) { %> <li> <%= comment.user.name %> <%= comment.description %> </li> <% }); %> </ul> 

However, comment.user.name will be undefined. If I try to just access the user property, for example comment.user, it will show its ID. Which tells me that it does not automatically populate the user information with the comment when I link the comment to another model.

Any any ideals to solve this correctly :)?

Thanks in advance!

PS

For clarification, here's how I basically set up associations in different models:

 // 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' } 
+44
nested associations populate
May 3, '14 at 15:39
source share
6 answers

Or you can use the built-in function 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); }); 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); });

Some explanation:

I use to work with arrays. For more information, see Note the return values ​​inside the first “next” function, those objects [post, commentsUsers] inside the array are also “promised” objects. This means that they did not contain data about the value when they first started, until they received the value. Thus, the spread function will wait until the attribute value arrives and continues to do the rest.
+44
Oct 19 '14 at 16:57
source share
— -

There is currently no built-in way to populate nested associations. It is best to use async to create a mapping:

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

This is not as good as a nested collection (which works, but probably not for v0.10), but on the bright side it is actually quite effective.

+26
May 04 '14 at 19:16
source share

It is worth saying that the add request to add a nested population: https://github.com/balderdashy/waterline/pull/1052

The Pull request is not merged at the moment, but you can use it by setting it directly with

 npm i Atlantis-Software/waterline#deepPopulate 

With it you can do something like .populate('user.comments ...)' .

+3
Jan 27 '16 at 23:08
source share
  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); } ); 
+3
Mar 25 '16 at 11:23
source share

You can use the async library, which is very simple and understandable. For each comment associated with the message, you can fill in as many fields as you want with selected tasks, execute them in parallel and get results when all tasks are completed. Finally, you must return the final result.

 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); }); }); 
+2
May 17 '16 at 8:46 a.m.
source share

I created an NPM module for this type of nested-pop. You can find it at the link below.

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

Use it as follows.

 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; ); 
+1
Oct 18 '16 at 0:30
source share



All Articles