Pour effectuer n'importe quel type de "regroupement" avec des requêtes MongoDB, vous souhaitez pouvoir utiliser le cadre d'agrégation ou mapReduce. Le cadre d'agrégation est généralement préféré car il utilise des opérateurs codés natifs plutôt que la traduction JavaScript, et est donc généralement plus rapide.
Les instructions d'agrégation ne peuvent être exécutées que du côté de l'API du serveur, ce qui est logique car vous ne voudriez pas le faire sur le client. Mais cela peut être fait là-bas et mettre les résultats à la disposition du client.
Avec attribution à cette réponse pour avoir fourni les méthodes de publication des résultats :
Meteor.publish("cardLikesDislikes", function(args) {
var sub = this;
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var pipeline = [
{ "$group": {
"_id": "$card_id",
"likes": {
"$sum": {
"$cond": [
{ "$eq": [ "$vote", 1 ] },
1,
0
]
}
},
"dislikes": {
"$sum": {
"$cond": [
{ "$eq": [ "$vote", 2 ] },
1,
0
]
}
},
"total": {
"$sum": {
"$cond": [
{ "$eq": [ "$vote", 1 ] },
1,
-1
]
}
}
}},
{ "$sort": { "total": -1 } }
];
db.collection("server_collection_name").aggregate(
pipeline,
// Need to wrap the callback so it gets called in a Fiber.
Meteor.bindEnvironment(
function(err, result) {
// Add each of the results to the subscription.
_.each(result, function(e) {
// Generate a random disposable id for aggregated documents
sub.added("client_collection_name", Random.id(), {
card: e._id,
likes: e.likes,
dislikes: e.dislikes,
total: e.total
});
});
sub.ready();
},
function(error) {
Meteor._debug( "Error doing aggregation: " + error);
}
)
);
});
La déclaration d'agrégation générale, il n'y a qu'un $group
opération sur la clé unique de "card_id". Afin d'obtenir les "j'aime" et les "je n'aime pas", vous utilisez une "expression conditionnelle" qui est $cond
.
C'est un opérateur "ternaire" qui considère un test logique sur la valeur de "vote", et où il correspond au type attendu puis un 1
positif est retourné, sinon c'est 0
.
Ces valeurs sont ensuite envoyées à l'accumulateur qui est $sum
pour les additionner et produire le nombre total pour chaque "card_id" par "j'aime" ou "je n'aime pas".
Pour le "total", la manière la plus efficace est d'attribuer une valeur "positive" pour "j'aime" et une valeur négative pour "je n'aime pas" en même temps que de faire le regroupement. Il y a un $add
opérateur, mais dans ce cas, son utilisation nécessiterait une autre étape du pipeline. Nous le faisons donc sur une seule étape à la place.
À la fin, il y a un $sort
dans l'ordre "décroissant" afin que le plus grand nombre de votes positifs soit en haut. Ceci est facultatif et vous souhaiterez peut-être simplement utiliser le tri dynamique côté client. Mais c'est un bon début pour une valeur par défaut qui supprime les frais généraux liés à cette opération.
Il s'agit donc de faire une agrégation conditionnelle et de travailler avec les résultats.
Tester la liste
C'est ce que j'ai testé avec le projet de météore nouvellement créé, sans addins et juste un seul modèle et un fichier javascript
commandes de la console
meteor create cardtest
cd cardtest
meteor remove autopublish
Création de la collection "cartes" dans la base de données avec les documents postés dans la question. Et puis édité les fichiers par défaut avec le contenu ci-dessous :
cardtest.js
Cards = new Meteor.Collection("cardStore");
if (Meteor.isClient) {
Meteor.subscribe("cards");
Template.body.helpers({
cards: function() {
return Cards.find({});
}
});
}
if (Meteor.isServer) {
Meteor.publish("cards",function(args) {
var sub = this;
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var pipeline = [
{ "$group": {
"_id": "$card_id",
"likes": { "$sum": { "$cond": [{ "$eq": [ "$vote", 1 ] },1,0] } },
"dislikes": { "$sum": { "$cond": [{ "$eq": [ "$vote", 2 ] },1,0] } },
"total": { "$sum": { "$cond": [{ "$eq": [ "$vote", 1 ] },1,-1] } }
}},
{ "$sort": { "total": -1, "_id": 1 } }
];
db.collection("cards").aggregate(
pipeline,
Meteor.bindEnvironment(
function(err,result) {
_.each(result,function(e) {
e.card_id = e._id;
delete e._id;
sub.added("cardStore",Random.id(), e);
});
sub.ready();
},
function(error) {
Meteor._debug( "error running: " + error);
}
)
);
});
}
cardtest.html
<head>
<title>cardtest</title>
</head>
<body>
<h1>Card aggregation</h1>
<table border="1">
<tr>
<th>Card_id</th>
<th>Likes</th>
<th>Dislikes</th>
<th>Total</th>
</tr>
{{#each cards}}
{{> card }}
{{/each}}
</table>
</body>
<template name="card">
<tr>
<td>{{card_id}}</td>
<td>{{likes}}</td>
<td>{{dislikes}}</td>
<td>{{total}}</td>
</tr>
</template>
Contenu final de la collection agrégée :
[
{
"_id":"Z9cg2p2vQExmCRLoM",
"likes":3,
"dislikes":1,
"total":2,
"card_id":1
},
{
"_id":"KQWCS8pHHYEbiwzBA",
"likes":2,
"dislikes":0,
"total":2,
"card_id":2
},
{
"_id":"KbGnfh3Lqcmjow3WN",
"likes":1,
"dislikes":0,
"total":1,
"card_id":3
}
]