Sails.js填充嵌套关联

问题描述 投票:47回答:8

我自己有一个关于Sails.js版本0.10-rc5中的关联的问题。我一直在构建一个应用程序,其中多个模型相互关联,我到达了一个我需要以某种方式获得嵌套关联的点。

有三个部分:

首先是博客文章,这是由用户编写的。在博客文章中,我想显示关联用户的信息,如用户名。现在,这里一切正常。直到下一步:我正在尝试显示与帖子相关的评论。

注释是一个单独的模型,称为Comment。每个人还有一个与之相关的作者(用户)。我可以轻松地显示评论列表,但是当我想显示与评论​​相关的用户信息时,我无法弄清楚如何使用用户的信息填充评论。

在我的控制器中,我试图做这样的事情:

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

在我的Post''show'动作中,我试图检索这样的信息(简化):

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

但是,comment.user.name将是未定义的。如果我尝试访问'user'属性,例如comment.user,它将显示它的ID。这告诉我,当我将评论与其他模型关联时,它不会自动将用户的信息填充到评论中。

任何理想的人都能妥善解决这个问题:)?

提前致谢!

附:

为了澄清,这就是我基本上在不同模型中建立关联的方式:

// 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'
}
node.js associations sails.js populate nested
8个回答
44
投票

或者你可以使用内置的Blue Bird Promise功能来制作它。 (致力于[email protected]

请参阅以下代码:

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来处理数组。有关详细信息,请参阅Official Doc
  2. 注意第一个“then”函数内的返回值,数组中的那些对象“[post,commentUsers]”也是“promise”对象。这意味着它们在首次执行时不包含值数据,直到它们获得值。因此,“传播”功能将等待动作值来继续做剩下的事情。

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),但从好的方面来看,它实际上相当有效。


3
投票

值得一提的是,有一个拉动请求来添加嵌套人口:https://github.com/balderdashy/waterline/pull/1052

Pull请求目前尚未合并,但您可以使用它直接安装

npm i Atlantis-Software/waterline#deepPopulate

有了它,你可以做像.populate('user.comments ...)'这样的事情。


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

3
投票

我为这个名为嵌套的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;
);

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

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

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

希望这可以帮助!

© www.soinside.com 2019 - 2024. All rights reserved.