Sails.js 填充嵌套关联

Posted

技术标签:

【中文标题】Sails.js 填充嵌套关联【英文标题】:Sails.js populate nested associations 【发布时间】:2014-06-20 05:17:56 【问题描述】:

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

分为三个部分:

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

cmets 是一个单独的模型,称为 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.
  );

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

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

comment.user.name 将是未定义的。如果我尝试仅访问“用户”属性,例如 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'

【问题讨论】:

【参考方案1】:

当然,这是一个老问题,但更简单的解决方案是遍历 cmets,使用 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;

希望这会有所帮助!

【讨论】:

【参考方案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');

【讨论】:

【参考方案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;
);

【讨论】:

我可以请您查看How to offer personal open-source libraries?吗? @Jam Risser 如何使用您的 NPM 模块获取 5 个级别的关联数据?【参考方案4】:

您可以使用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);
            );
        );

【讨论】:

【参考方案5】:
 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);
   
);

【讨论】:

【参考方案6】:

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

Pull 请求目前没有合并,但您可以使用它直接安装一个

npm i Atlantis-Software/waterline#deepPopulate

有了它,您可以执行.populate('user.comments ...)' 之类的操作。

【讨论】:

是否支持 MSSQL 适配器? @RajAdroit 不知道,我没用过 waterline wuth mssql【参考方案7】:

或者你可以使用内置的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);
  );

一些解释:

    我使用Lo-Dash 来处理数组。更多详情请参考Official Doc 注意第一个“then”函数中的返回值,数组中的那些对象“[post, commentUsers]”也是“promise”对象。这意味着它们在第一次执行时不包含值数据,直到它们获得值。这样“传播”功能将等待 acture 值的到来并继续做剩下的事情。

【讨论】:

您能否解释一下这一行:_.pluck(results.post.comments, 'user')?我没有看到 results 是在哪里声明的。 我刚刚看了你的一些其他答案。我可以告诉你知识渊博,但我会要求你详细说明一下。不要只提供代码,提供解释性答案。欢迎加入社区。​​span> 感谢@colepanike 的好意建议,我刚刚修复了我的代码 sn-p 以使其正确并添加了一些解释。【参考方案8】:

目前,没有内置方法来填充嵌套关联。最好的办法是使用 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);
   
);

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

【讨论】:

我按照你的逻辑进行了更复杂的深度关联,一切似乎都很好,但是在对 JSON 对象进行字符串化时出现了问题,它省略了一些字段。你能看一下吗:***.com/questions/25142731/…? 你将如何为多对多关联执行此操作?在那种情况下,我什至不会得到对象的 id,只是一个未定义的值。获取对象后没有办法 .populate() 吗? 很好的 async.auto() 用法。 async.waterfall 不是更适合这里吗? 谁能帮帮我:***.com/questions/29588582/…

以上是关于Sails.js 填充嵌套关联的主要内容,如果未能解决你的问题,请参考以下文章

Sails.js:MySQL 中缺少水线外键关联

从 Sails.js 中的关联模型访问模型的属性

Sails.js + Waterline:通过关联进行多对多

如何在 Sails.js 中定义需要来自第三个模型/表的附加条件和/或数据的模型关联?

Rails form_for 具有相同属性的嵌套和关联资源已填充文本

使用 hasMany 关联在 ExtJS 中填充存储