node.js - query - sails sort
Sails.js puebla asociaciones anidadas (7)
Tengo una pregunta sobre las asociaciones en Sails.js versión 0.10-rc5. He estado creando una aplicación en la que varios modelos están asociados entre sí, y llegué a un punto en el que necesito llegar a asociaciones de nidos de alguna manera.
Hay tres partes:
Primero hay algo así como una publicación de blog, que está siendo escrita por un usuario. En la publicación del blog, quiero mostrar la información del usuario asociado como su nombre de usuario. Ahora, todo funciona bien aquí. Hasta el siguiente paso: estoy tratando de mostrar los comentarios que están asociados con la publicación.
Los comentarios son un Modelo separado, llamado Comentario. Cada uno de los cuales también tiene un autor (usuario) asociado. Puedo mostrar fácilmente una lista de los Comentarios, aunque cuando quiero mostrar la información del Usuario asociada con el comentario, no puedo encontrar la manera de completar el Comentario con la información del usuario.
En mi controlador, estoy tratando de hacer algo como esto:
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.
});
En la acción ''mostrar'' de mi publicación, intento recuperar la información de esta manera (simplificada):
<ul>
<%- _.each(post.comments, function(comment) { %>
<li>
<%= comment.user.name %>
<%= comment.description %>
</li>
<% }); %>
</ul>
El comment.user.name no estará definido. Si trato de acceder a la propiedad ''user'', como comment.user, mostrará su ID. Lo que me dice que no está rellenando automáticamente la información del usuario con el comentario cuando asocio el comentario con otro modelo.
Alguien algún ideal para resolver esto correctamente :)?
¡Gracias por adelantado!
PD
Para aclarar, así es como básicamente he configurado las asociaciones en diferentes modelos:
// 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''
}
A partir de sailsjs 1.0, la https://github.com/balderdashy/waterline/pull/1052 aún está abierta, pero la siguiente solución de función asíncrona se ve lo suficientemente elegante como IMO:
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'');
}
Creé un módulo de NPM para esto llamado pop anidado . Puede encontrarlo en el siguiente enlace.
https://www.npmjs.com/package/nested-pop
Úselo de la siguiente manera.
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;
);
O puede usar la función integrada Blue Bird Promise para hacerlo. (Trabajando en [email protected])
Vea los códigos a continuación:
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);
});
Alguna explicación:
- Estoy usando Lo-Dash para manejar las matrices. Para obtener más detalles, consulte el documento Lo-Dash
- Observe los valores de retorno dentro de la primera función "then", esos objetos "[post, commentUsers]" dentro de la matriz también son objetos "prometedores". Lo que significa que no contenían los datos de valor cuando se ejecutaron por primera vez, hasta que obtuvieron el valor. De modo que la función de "propagación" esperará el valor de la acción y continuará haciendo el resto.
Por el momento, no hay una forma integrada para poblar asociaciones anidadas. Su mejor opción es usar asincrónico para hacer un mapeo:
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);
}
);
No es tan bonita como la población anidada (que está en proceso, pero probablemente no para la v0.10), pero por el lado bueno, en realidad es bastante eficiente.
Puede usar la biblioteca async , que es muy clara y fácil de entender. Para cada comentario relacionado con una publicación, puede rellenar muchos campos como desee con tareas dedicadas, ejecutarlos en paralelo y recuperar los resultados cuando finalicen todas las tareas. Finalmente, solo tienes que devolver el resultado 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);
});
});
Vale la pena decir que hay una solicitud de extracción para agregar población anidada: https://github.com/balderdashy/waterline/pull/1052
La solicitud de extracción no está fusionada en este momento, pero puede usarla instalando una directamente con
npm i Atlantis-Software/waterline#deepPopulate
Con él puedes hacer algo como .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);
}
);