Average Aggregation Queries in Meteor

As of Meteor 0.6.5, the collection API doesn’t support aggregation queries yet because there’s no (straightforward) way to do live updates on them. However, you can still write them yourself, and make them available in a Meteor.publish, although the result will be static. In my opinion, doing it this way is still preferable because you can merge multiple aggregations and use the client-side collection API.

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

The above is an example grouping/count aggregation. Some things of note:

  • When you do this, you’ll naturally be doing an aggregation on server_collection_name and pushing the results to a different collection called client_collection_name.
  • This subscription isn’t going to be live, and will probably be updated whenever the arguments change, so we use a really simple loop that just pushes all the results out.
  • The results of the aggregation don’t have Mongo ObjectIDs, so we generate some arbitrary ones of our own.
  • The callback to the aggregation needs to be wrapped in a Fiber. I use Meteor.bindEnvironment here but one can also use a Future for more low-level control.

If you start combining the results of publications like these, you’ll need to carefully consider how the randomly generated ids impact the merge box. However, a straightforward implementation of this is just a standard database query, except it is more convenient to use with Meteor APIs client-side.

TL;DR version: Almost anytime you are pushing data out from the server, a publish is preferable to a method.

For more information about different ways to do aggregation, check out this post.

Leave a Comment