Query di aggregazione media in Meteor

Ok, ancora nella mia app giocattolo, voglio scoprire il chilometraggio medio su un gruppo di contachilometri di proprietari di auto. Questo è abbastanza semplice per il cliente ma non è scalabile. Destra? Ma sul server, non vedo esattamente come realizzarlo.

Domande:

  1. Come si implementa qualcosa sul server e poi lo si usa sul client?
  2. Come usi la funzione di aggregazione $ avg di mongo per sfruttare la sua funzione di aggregazione ottimizzata?
  3. O in alternativa a (2) come si fa una mappa / ridurre sul server e renderlo disponibile al client?

Il suggerimento di @HubertOG era di usare Meteor.call, il che ha senso e l’ho fatto:

# Client side Template.mileage.average_miles = -> answer = null Meteor.call "average_mileage", (error, result) -> console.log "got average mileage result #{result}" answer = result console.log "but wait, answer = #{answer}" answer # Server side Meteor.methods average_mileage: -> console.log "server mileage called" total = count = 0 r = Mileage.find({}).forEach (mileage) -> total += mileage.mileage count += 1 console.log "server about to return #{total / count}" total / count 

Sembrerebbe funzionare bene, ma non perché il più vicino che posso dire a Meteor.call è una chiamata asincrona e la answer sarà sempre un ritorno nullo. Trattare cose sul server sembra un caso d’uso abbastanza comune che devo aver semplicemente trascurato qualcosa. Cosa sarebbe quello?

Grazie!

A partire da Meteor 0.6.5, l’API di raccolta non supporta ancora le query di aggregazione perché non esiste un (semplice) modo di fare aggiornamenti in tempo reale su di esse. Tuttavia, puoi ancora scriverli da solo e renderli disponibili in un Meteor.publish , anche se il risultato sarà statico. Secondo me, continuare a farlo in questo modo è ancora preferibile perché puoi unire più aggregazioni e utilizzare l’API di raccolta lato client.

 Meteor.publish("someAggregation", function (args) { var sub = this; // This works for Meteor 0.6.5 var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db; // Your arguments to Mongo's aggregation. Make these however you want. var pipeline = [ { $match: doSomethingWith(args) }, { $group: { _id: whatWeAreGroupingWith(args), count: { $sum: 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(), { key: e._id.somethingOfInterest, count: e.count }); }); sub.ready(); }, function(error) { Meteor._debug( "Error doing aggregation: " + error); } ) ); }); 

Quanto sopra è un esempio di raggruppamento / conteggio dell’aggregazione. Alcune cose importanti:

  • Quando lo fai, server_collection_name naturalmente un’aggregazione su server_collection_name e server_collection_name i risultati in una raccolta diversa denominata client_collection_name .
  • Questa iscrizione non sarà live e verrà probabilmente aggiornata ogni volta che gli argomenti cambiano, quindi usiamo un ciclo davvero semplice che semplicemente spinge tutti i risultati.
  • I risultati dell’aggregazione non hanno IDO di Mongo, quindi ne generiamo di propri arbitrari.
  • Il callback all’aggregazione deve essere racchiuso in una fibra. Io uso Meteor.bindEnvironment qui, ma si può anche usare un Future per un controllo più di basso livello.

Se inizi a combinare i risultati di pubblicazioni come queste, dovrai considerare attentamente come gli ID generati casualmente influenzano la finestra di fusione. Tuttavia, un’implementazione diretta di questo è solo una query di database standard, eccetto che è più comoda da usare con le API di Meteor sul lato client.

Versione TL; DR : quasi ogni volta che si estrae dati dal server, una publish è preferibile a un method .

Per ulteriori informazioni sui diversi modi di fare aggregazione, controlla questo post .

L’ho fatto con il metodo “aggregato”. (ver 0.7.x)

 if(Meteor.isServer){ Future = Npm.require('fibers/future'); Meteor.methods({ 'aggregate' : function(param){ var fut = new Future(); MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){ fut.return(result); }); return fut.wait(); } ,'test':function(param){ var _param = { pipe : [ { $unwind:'$data' }, { $match:{ 'data.y':"2031", 'data.m':'01', 'data.d':'01' }}, { $project : { '_id':0 ,'project_id' : "$project_id" ,'idx' : "$data.idx" ,'y' : '$data.y' ,'m' : '$data.m' ,'d' : '$data.d' }} ], collection:"yourCollection" } Meteor.call('aggregate',_param); } }); 

}

Se vuoi reattività, usa Meteor.publish invece di Meteor.call . C’è un esempio nei documenti in cui pubblicano il numero di messaggi in una determinata stanza (appena sopra la documentazione per this.userId ), dovresti essere in grado di fare qualcosa di simile.

Puoi usare Meteor.methods per quello.

 // server Meteor.methods({ average: function() { ... return something; }, }); // client var _avg = { /* Create an object to store value and dependency */ dep: new Deps.Dependency(); }; Template.mileage.rendered = function() { _avg.init = true; }; Template.mileage.averageMiles = function() { _avg.dep.depend(); /* Make the function rerun when _avg.dep is touched */ if(_avg.init) { /* Fetch the value from the server if not yet done */ _avg.init = false; Meteor.call('average', function(error, result) { _avg.val = result; _avg.dep.changed(); /* Rerun the helper */ }); } return _avg.val; });