我在Sails.js版本0.10-rc5中遇到了关于关联的问题。我已经构建了一个应用程序,其中多个模型彼此关联,并且我已经到达了我需要以某种方式嵌套关联的位置。
有三个部分:
首先有一个类似博客文章的东西,这是由用户编写的。在博客文章中,我想显示关联用户的信息,例如他们的用户名。现在,这里一切正常。直到下一步:我试图显示与帖子相关的评论。
评论是一个单独的模型,称为评论。每个人还有一个与之相关的作者(用户)。我可以轻松地显示评论列表,但是当我想显示与评论相关的用户信息时,我无法弄清楚如何使用用户的信息填充评论。
在我的控制器中,我试图做这样的事情:
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.
});
在我的帖子'节目'我试图检索这样的信息(简化):
<ul>
<%- _.each(post.comments, function(comment) { %>
<li>
<%= comment.user.name %>
<%= comment.description %>
</li>
<% }); %>
</ul>
但是,comment.user.name将是未定义的。如果我尝试访问“用户”#39;属性,如comment.user,它会显示它的ID。这告诉我,当我将评论与其他模型相关联时,它不会自动将用户的信息填充到评论中。
任何理想的人都可以妥善解决这个问题:)?
提前致谢!
P.S。
为了澄清,这就是我在不同模型中基本建立关联的方式:
// 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'
}
答案 0 :(得分:44)
或者您可以使用内置的Blue Bird Promise功能来制作它。 (致力于Sails@v0.10.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 :(得分:26)
目前,没有内置的方法来填充嵌套关联。最好的办法是使用异步来进行映射:
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);
}
);
它不像嵌套人口那样漂亮(在工作中,但可能不适用于v0.10),但从好的方面来看,它实际上相当有效。
答案 2 :(得分:3)
值得一提的是添加嵌套群体的拉取请求:https://github.com/balderdashy/waterline/pull/1052
目前尚未合并拉取请求,但您可以使用
直接安装拉取请求npm i Atlantis-Software/waterline#deepPopulate
有了它,您可以执行.populate('user.comments ...)'
。
答案 3 :(得分:3)
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);
}
);
答案 4 :(得分:3)
我为这个名为 nested-pop 的创建了一个NPM模块。您可以在下面的链接中找到它。
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;
);
答案 5 :(得分:2)
您可以使用非常简洁易懂的async库。对于与帖子相关的每个评论,您可以根据需要使用专用任务填充许多字段,并行执行它们并在完成所有任务后检索结果。最后,您只需要返回最终结果。
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);
});
});
答案 6 :(得分:2)
截至sailsjs 1.0,"deep populate" pull request仍处于打开状态,但以下异步功能解决方案看起来足够优雅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');
}
答案 7 :(得分:0)
这是一个古老的问题,但是更简单的解决方案是遍历注释,使用async await将每个注释的'user'属性(它是一个id)替换为用户的完整详细信息。
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;
}
希望这会有所帮助!