За да направите каквото и да е "групиране" със заявки на MongoDB, тогава искате да можете да използвате рамката за агрегиране или mapReduce. Рамката за агрегиране обикновено се предпочита, тъй като използва собствени кодирани оператори, а не превод на JavaScript, и следователно обикновено е по-бърза.
Изявленията за агрегиране могат да се изпълняват само от страна на API на сървъра, което има смисъл, защото не бихте искали да правите това на клиента. Но това може да се направи там и да се направят резултатите достъпни за клиента.
С приписване на този отговор за предоставяне на методите за публикуване на резултати:
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);
}
)
);
});
Общият оператор за агрегиране там е просто $group
операция с единичния ключ на "card_id". За да получите "харесва" и "не харесва", използвате "условен израз", който е $cond
.
Това е "троичен" оператор, който разглежда логически тест за стойността на "vote" и когато съвпада с типа очакване, тогава положителен 1
се връща, в противен случай е 0
.
След това тези стойности се изпращат до акумулатора, който е $sum
за да ги добавите заедно и да получите общия брой за всеки „card_id“ чрез „харесвам“ или „не харесвам“.
За „общо“ най-ефективният начин е да се припише „положителна“ стойност за „харесвам“ и отрицателна стойност за „не харесвам“ едновременно с извършването на групирането. Има $add
оператор, но в този случай използването му ще изисква друг етап на конвейера. Затова вместо това го правим на един етап.
В края на това има $sort
в "низходящ" ред, така че най-големият брой положителни гласове е на върха. Това е по избор и може просто да искате да използвате динамично сортиране от страна на клиента. Но това е добро начало за настройка по подразбиране, която премахва излишните разходи за това.
Така че това е извършване на условно агрегиране и работа с резултатите.
Тестов списък
Това е, което тествах с новосъздадения проект за метеор, без добавки и само с един шаблон и javascript файл
конзолни команди
meteor create cardtest
cd cardtest
meteor remove autopublish
Създаде колекцията "карти" в базата данни с документите, публикувани във въпроса. И след това редактира файловете по подразбиране със съдържанието по-долу:
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>
Окончателно обобщено съдържание на колекция:
[
{
"_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
}
]