node.js Sails.js 填充嵌套关联

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23446484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 17:15:50  来源:igfitidea点击:

Sails.js populate nested associations

node.jsassociationssails.jspopulatenested

提问by Lars Dol

I've got myself a question regarding associations in Sails.js version 0.10-rc5. I've been building an app in which multiple models are associated to one another, and I've arrived at a point where I need to get to nest associations somehow.

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

There's three parts:

有三部分:

First there's something like a blog post, that's being written by a user. In the blog post I want to show the associated user's information like their username. Now, everything works fine here. Until the next step: I'm trying to show comments which are associated with the post.

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

The comments are a separate Model, called Comment. Each of which also has an author (user) associated with it. I can easily show a list of the Comments, although when I want to display the User's information associated with the comment, I can't figure out how to populate the Comment with the user's information.

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

In my controller i'm 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 my Post's 'show' action i'm trying to retrieve the information like this (simplified):

在我的帖子的“显示”操作中,我试图检索这样的信息(简化):

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

The comment.user.name will be undefined though. If I try to just access the 'user' property, like comment.user, it'll show it's ID. Which tells me it's not automatically populating the user's information to the comment when I associate the comment with another model.

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

Anyone any ideals to solve this properly :)?

任何人有任何理想来正确解决这个问题:)?

Thanks in advance!

提前致谢!

P.S.

聚苯乙烯

For clarification, this is how i've basically set up the 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'
}

回答by Fermin Yang

Or you can use the built-in Blue BirdPromise feature to make it. (Working on [email protected])

或者您可以使用内置的Blue BirdPromise 功能来实现。(在 [email protected] 上工作)

See the codes below:

请参阅以下代码:

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:

一些解释:

  1. I'm using the Lo-Dashto deal with the arrays. For more details, please refer to the Official Doc
  2. Notice the return values inside the first "then" function, those objects "[post, commentUsers]" inside the array are also "promise" objects. Which means that they didn't contain the value data when they first been executed, until they got the value. So that "spread" function will wait the acture value come and continue doing the rest stuffs.
  1. 我正在使用Lo-Dash来处理数组。更多详情请参考官方文档
  2. 注意第一个“then”函数内的返回值,数组内的那些对象“[post, commentUsers]”也是“promise”对象。这意味着它们在第一次执行时不包含值数据,直到它们获得值。所以“传播”函数将等待实际值来并继续做其余的事情。

回答by sgress454

At the moment, there's no built in way to populate nested associations. Your best bet is to use async to do a mapping:

目前,没有内置的方法来填充嵌套关联。最好的办法是使用 async 进行映射:

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

It's not as pretty as nested population (which is in the works, but probably not for v0.10), but on the bright side it's actually fairly efficient.

它不像嵌套人口那么漂亮(正在开发中,但可能不适用于 v0.10),但从好的方面来说,它实际上相当有效。

回答by Jam Risser

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

我为此创建了一个 NPM 模块,称为nested-pop。您可以在下面的链接中找到它。

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

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

Use it in the following way.

按照以下方式使用它。

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

回答by Glen Swift

Worth saying there's a pull request to add nested population: https://github.com/balderdashy/waterline/pull/1052

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

Pull request isn't merged at the moment but you can use it installing one directly with

拉取请求目前未合并,但您可以使用它直接安装一个

npm i Atlantis-Software/waterline#deepPopulate

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

有了它,你可以做类似的事情.populate('user.comments ...)'

回答by Aravind Kumar

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

回答by Mariusz Wiazowski

You could use asynclibrary which is very clean and simple to understand. For each comment related to a post you can populate many fields as you want with dedicated tasks, execute them in parallel and retrieve the results when all tasks are done. Finally, you only have to 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);
            });
        });

回答by har-wradim

As of sailsjs 1.0 the "deep populate" pull requestis still open, but the following async function solution looks elegant enough IMO:

从sailsjs 1.0 开始,“深度填充”拉取请求仍处于打开状态,但以下异步函数解决方案在 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');
}

回答by Adim Victor

Granted this is an old question, but a much simpler solution would be to loop over the comments,replacing each comment's 'user' property (which is an id) with the user's full detail using async await.

当然,这是一个老问题,但更简单的解决方案是循环评论,使用异步等待将每个评论的“用户”属性(这是一个 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;
}

Hope this helps!

希望这可以帮助!