0

I want to do exactly what this SO question gets at but with Meteor on the server side:

How do I retrieve all of the documents which HAVE a unique value of a field?

> db.foo.insert([{age: 21, name: 'bob'}, {age: 21, name: 'sally'}, {age: 30, name: 'Jim'}])
> db.foo.count()
3
> db.foo.aggregate({ $group: { _id: '$age', name: { $max: '$name' } } }).result
[
    {
        "_id" : 30,
        "name" : "Jim"
    },
    {
        "_id" : 21,
        "name" : "sally"
    }
]

My understanding is that aggregate is not available for Meteor. If that is correct, how can I achieve the above? Performing post-filtering on a query after-the-fact is not an ideal solution, as I want to use limit. I'm also happy to get documents with a unique field some other way as long as I can use limit.

Community
  • 1
  • 1
FullStack
  • 5,902
  • 4
  • 43
  • 77
  • It's not available directly as bundled, but there are meteorites you can install to enable it. Also you can get this by accessing the underlying driver as well. It's only ever available on the "server" though, as on the "client" it would not make any sense anyway, and hence why not a standard inclusion. Quite a few exaples around. I have written some myself. – Blakes Seven Sep 09 '15 at 07:25
  • @BlakesSeven - can you point me to examples on how I access the underlying driver to solve my problem? I've searched but couldn't find any. – FullStack Sep 09 '15 at 07:27

1 Answers1

1

There is a general setup you can use to access the underlying driver collection object and therefore .aggregate() without installing any other plugins.

The basic process goes like this:

FooAges = new Meteor.Collection("fooAges");

Meteor.publish("fooAgeQuery", function(args) {
    var sub = this;

    var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;

    var pipeline = [
        { "$group": {
            "_id": "$age", 
            "name": { "$max": "$name" }
        }}
    ];

    db.collection("foo").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("fooAges", Random.id(), {
                        "age": e._id,
                        "name": e.name
                    });
                });
                sub.ready();
            },
            function(error) {
                Meteor._debug( "Error doing aggregation: " + error);
            }
        )
    );

});

So you define a collection for the output of the aggregation and within a routine like this you then publish the service that you are also going to subscribe to in your client.

Inside this, the aggregation is run and populated into the the other collection ( logically as it doesn't actually write anything ). So you then use that collection on the client with the same definition and all the aggregated results are just returned.

I actually have a full working example application of a similar processs within this question, as well as usage of the meteor hacks aggregate package on this question here as well, if you need further reference.

Community
  • 1
  • 1
Blakes Seven
  • 49,422
  • 14
  • 129
  • 135