J'ai moi-même une question concernant les associations dans Sails.js version 0.10-rc5. J'ai construit une application dans laquelle plusieurs modèles sont associés les uns aux autres, et je suis arrivé à un point où je dois arriver à imbriquer les associations d'une manière ou d'une autre.
Il y a trois parties:
Il y a d'abord quelque chose comme un blog, qui est écrit par un utilisateur. Dans le billet de blog, je souhaite afficher les informations de l'utilisateur associé comme son nom d'utilisateur. Maintenant, tout fonctionne bien ici. Jusqu'à l'étape suivante: j'essaie d'afficher les commentaires associés au message.
Les commentaires sont un modèle distinct, appelé Commentaire. Chacun d'eux a également un auteur (utilisateur) qui lui est associé. Je peux facilement afficher une liste des commentaires, bien que lorsque je souhaite afficher les informations de l'utilisateur associées au commentaire, je n'arrive pas à comprendre comment remplir le commentaire avec les informations de l'utilisateur.
Dans mon contrôleur, j'essaie de faire quelque chose comme ça:
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.
});
Dans l'action "show" de mon article, j'essaie de récupérer les informations comme ceci (simplifiées):
<ul>
<%- _.each(post.comments, function(comment) { %>
<li>
<%= comment.user.name %>
<%= comment.description %>
</li>
<% }); %>
</ul>
Le comment.user.name ne sera cependant pas défini. Si j'essaie simplement d'accéder à la propriété 'user', comme comment.user, cela montrera son ID. Ce qui m'indique qu'il ne remplit pas automatiquement les informations de l'utilisateur dans le commentaire lorsque j'associe le commentaire à un autre modèle.
Quelqu'un a-t-il des idéaux pour résoudre cela correctement :)?
Merci d'avance!
P.S.
Pour plus de précision, voici comment j'ai essentiellement mis en place les associations dans différents modèles:
// 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'
}
Ou vous pouvez utiliser la fonction intégrée Blue Bird Promise pour le faire. (Travailler sur [email protected])
Voir les codes ci-dessous:
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);
});
Quelques explications:
Pour le moment, il n'existe aucun moyen intégré pour peupler les associations imbriquées. Le mieux est d'utiliser async pour faire un mappage:
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);
}
);
Ce n'est pas aussi joli que la population imbriquée (qui est en préparation, mais probablement pas pour la v0.10), mais du côté positif, c'est en fait assez efficace.
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);
}
);
J'ai créé un module NPM pour cela appelé nested-pop . Vous pouvez le trouver sur le lien ci-dessous.
https://www.npmjs.com/package/nested-pop
Utilisez-le de la manière suivante.
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;
);
Il vaut la peine de dire qu'il existe une demande d'extraction pour ajouter une population imbriquée: https://github.com/balderdashy/waterline/pull/1052
La requête Pull n'est pas fusionnée pour le moment mais vous pouvez l'utiliser en en installant une directement avec
npm i Atlantis-Software/waterline#deepPopulate
Avec lui, vous pouvez faire quelque chose comme .populate('user.comments ...)'
.
Depuis sailsjs 1.0, la "pull pull" est toujours ouverte, mais la solution de fonction asynchrone suivante semble assez élégante IMO:
const post = await Post
.findOne({ id: req.param('id') })
.populate('user')
.populate('comments');
if (post && post.comments.length > 0) {
const ids = post.comments.map(comment => comment.id);
post.comments = await Comment
.find({ id: commentId })
.populate('user');
}
Vous pouvez utiliser la bibliothèque async qui est très propre et simple à comprendre. Pour chaque commentaire lié à une publication, vous pouvez remplir de nombreux champs comme vous le souhaitez avec des tâches dédiées, les exécuter en parallèle et récupérer les résultats lorsque toutes les tâches sont terminées. Enfin, il vous suffit de renvoyer le résultat final.
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);
});
});
Certes, c'est une vieille question, mais une solution beaucoup plus simple serait de parcourir les commentaires, en remplaçant la propriété "utilisateur" de chaque commentaire (qui est un identifiant) par tous les détails de l'utilisateur utilisant async.
async function getPost(postId){
let post = await Post.findOne(postId).populate('user').populate('comments');
for(let comment of post.comments){
comment.user = await User.findOne({id:comment.user});
}
return post;
}
J'espère que cela t'aides!