web-dev-qa-db-fra.com

Sails.js remplit les associations imbriquées

J'ai moi-même une question concernant les associations dans Sails.js version 0.10-rc5. J'ai construit une application dans laquelle plusieurs modèles sont associés les uns aux autres, et je suis arrivé à un point où je dois arriver à imbriquer les associations d'une manière ou d'une autre.

Il y a trois parties:

Il y a d'abord quelque chose comme un blog, qui est écrit par un utilisateur. Dans le billet de blog, je souhaite afficher les informations de l'utilisateur associé comme son nom d'utilisateur. Maintenant, tout fonctionne bien ici. Jusqu'à l'étape suivante: j'essaie d'afficher les commentaires associés au message.

Les commentaires sont un modèle distinct, appelé Commentaire. Chacun d'eux a également un auteur (utilisateur) qui lui est associé. Je peux facilement afficher une liste des commentaires, bien que lorsque je souhaite afficher les informations de l'utilisateur associées au commentaire, je n'arrive pas à comprendre comment remplir le commentaire avec les informations de l'utilisateur.

Dans mon contrôleur, j'essaie de faire quelque chose comme ça:

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

Dans l'action "show" de mon article, j'essaie de récupérer les informations comme ceci (simplifiées):

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

Le comment.user.name ne sera cependant pas défini. Si j'essaie simplement d'accéder à la propriété 'user', comme comment.user, cela montrera son ID. Ce qui m'indique qu'il ne remplit pas automatiquement les informations de l'utilisateur dans le commentaire lorsque j'associe le commentaire à un autre modèle.

Quelqu'un a-t-il des idéaux pour résoudre cela correctement :)?

Merci d'avance!

P.S.

Pour plus de précision, voici comment j'ai essentiellement mis en place les associations dans différents modèles:

// 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'
}
48
Darkstra

Ou vous pouvez utiliser la fonction intégrée Blue Bird Promise pour le faire. (Travailler sur [email protected])

Voir les codes ci-dessous:

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

Quelques explications:

  1. J'utilise Lo-Dash pour gérer les tableaux. Pour plus de détails, veuillez vous référer au Doc officiel
  2. Remarquez les valeurs de retour à l'intérieur de la première fonction "then", ces objets "[post, commentUsers]" à l'intérieur du tableau sont également des objets "promise". Ce qui signifie qu'ils ne contenaient pas les données de valeur lors de leur première exécution, jusqu'à ce qu'ils obtiennent la valeur. Pour que la fonction "spread" attende que la valeur d'acture vienne et continue à faire le reste.
45
Fermin Yang

Pour le moment, il n'existe aucun moyen intégré pour peupler les associations imbriquées. Le mieux est d'utiliser async pour faire un mappage:

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

Ce n'est pas aussi joli que la population imbriquée (qui est en préparation, mais probablement pas pour la v0.10), mais du côté positif, c'est en fait assez efficace.

26
sgress454
 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
Aravind Kumar

J'ai créé un module NPM pour cela appelé nested-pop . Vous pouvez le trouver sur le lien ci-dessous.

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

Utilisez-le de la manière suivante.

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;
);
3
Jam Risser

Il vaut la peine de dire qu'il existe une demande d'extraction pour ajouter une population imbriquée: https://github.com/balderdashy/waterline/pull/1052

La requête Pull n'est pas fusionnée pour le moment mais vous pouvez l'utiliser en en installant une directement avec

npm i Atlantis-Software/waterline#deepPopulate

Avec lui, vous pouvez faire quelque chose comme .populate('user.comments ...)'.

3
Glen Swift

Depuis sailsjs 1.0, la "pull pull" est toujours ouverte, mais la solution de fonction asynchrone suivante semble assez élégante 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');
}
2
har-wradim

Vous pouvez utiliser la bibliothèque async qui est très propre et simple à comprendre. Pour chaque commentaire lié à une publication, vous pouvez remplir de nombreux champs comme vous le souhaitez avec des tâches dédiées, les exécuter en parallèle et récupérer les résultats lorsque toutes les tâches sont terminées. Enfin, il vous suffit de renvoyer le résultat 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);
            });
        });
2

Certes, c'est une vieille question, mais une solution beaucoup plus simple serait de parcourir les commentaires, en remplaçant la propriété "utilisateur" de chaque commentaire (qui est un identifiant) par tous les détails de l'utilisateur utilisant async.

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

J'espère que cela t'aides!

0
Adim Victor