Mongodb aggregate

Is there a new way to use the mongodb aggregate function that doesn’t require the meteorhacks:aggregate or is that still the only way to go.

I’d probably recommend using rawCollection() and the official npm MongoDB library aggregate method. So in a Meteor method, for example:

Meteor.methods({
  async aggregateMyStuff() {
    const pipeline = [ ... ];
    const options = { ... };
    return MyCollection.rawCollection().aggregate(pipeline, options);
  },
});
12 Likes

Slightly off topic but would you say it’s preferable to use rawCollection() everywhere (find, findOne, count, etc) to make use of promises?

Hmm. If you want to make use of Meteor’s pub/sub, you probably won’t be able to use it everywhere. The cursor Meteor uses is not the same as the npm library’s cursor.

Having said that, if you’re using methods, or you’re happy to drop back to minimongo for publications, it makes some sense. Especially if you want to use some of the many methods available with the official library.

Of course, you can’t use rawCollection() on the client.

I ended up doing something that you might be interested in:

class SuperCollection extends Meteor.Collection {
  async aggregate(pipeline = []) {
    return this.rawCollection().aggregate(pipeline, { allowDiskUse: true }).toArray();
  }

  async pCount(query = {}, options = {}) {
    return this.rawCollection().count(query, options);
  }

  async pFindOne(query = {}, options = {}) {
    if (typeof query === 'string') query = { _id: query };
    return this.rawCollection().findOne(query, options);
  }

  async distinct(key = '', query = {}, options = {}) {
    return this.rawCollection().distinct(key, query, options);
  }

  async ensureIndex(fieldOrSpec, options = {}) {
    return this.rawCollection().ensureIndex(fieldOrSpec, options);
  }
};
3 Likes

Sorry, can you explain your code…what will be the use case?

It allows me to make a collection with const Collection = new SuperCollection('collectionName'); and then I have the ability to do Collection.aggregate instead of Collection.rawCollection().aggregate throughout the (server-side) codebase. It also adds distinct, ensureIndex, an async count, and an async findOne.

1 Like

I’ve been using this one to publish aggregations in meteor: lamoglia:publish-aggregation. It also uses rawCollection to perform the aggregations.

1 Like

Hi, I base on Vue.
I don’t use async, but it still work fine

// Vue Method
methods:{
      rawCollectionTest
        .callPromise({})
        .then(result => {
          if (result) {
            this.rawData = result
          }
        })
        .catch(error => {
          console.log(error)
        })
}
---
// Meteor Method
export const rawCollectionTest = new ValidatedMethod({
  name: 'app.rawCollectionTest',
  mixins: [CallPromiseMixin],
  validate: new SimpleSchema({
    selector: {
      type: Object,
      optional: true,
      blackbox: true,
    },
  }).validator(),
  run({ selector }) {
    if (Meteor.isServer) {
      selector = selector || {}
      const data = Branches.rawCollection()
        .aggregate([
          {
            $match: selector,
          },
          {
            $project: {
              _id: 1,
              name: 1,
              address: 1,
            },
          },
        ])
        .toArray()

      return data
    }
  },
})

And then I tried use async/await, It work fine too.
Please advise???

1 Like

But I can use forEach data in server method

const data = Branches.rawCollection()
        .aggregate([
          {
            $match: selector,
          },
          {
            $project: {
              _id: 1,
              name: 1,
              address: 1,
            },
          },
        ])
        .toArray()

   data.forEach(.......) // don't work

depending on the mongodb version, aggregate will return a cursor. If you call toArray() on it, you get a Promise. You have to use async/await in this case

1 Like

thanks for your explain.

@bmanturner, I would like to use Meteor.user(), Meteor.userId() in Extends Collection Class.
But don’t work, Pl help me

class SuperCollection extends Meteor.Collection {
  async aggregate(pipeline = []) {
    // I would like to get current user: Meteor.user(), Meteor.userId()
    // but not work
    ...
    return this.rawCollection().aggregate(pipeline, { allowDiskUse: true }).toArray();
  }
  1. what’s the error?
  2. you could also pass it as param to your aggregate method

Get error, when I fixture data on startup

Error: Meteor.userId can only be invoked in method calls or publications

the error does exactly describe what’s going on. You can only call Meteor.userId() from a method call or in a publication.

This makes perfect sense: you can only know the current user in these two context.

In server startup, its pretty clear, that you can’t do that as there is no user.

1 Like

Here was some stuff because I was stupid! :smile:

The current MongoDB library returns a Promise to an aggregation cursor for this. Are you sure you’re looking for the right thing?

On the assumption that’s a Meteor Method, maybe something like:

async testAggregate() {
  const buyer = Meteor.user();
  let $match = { buyer_id: buyer._id };
  $match.datetime = { $gte: moment().subtract(1, 'month').toDate() };
  try {
    const orderedItems = await OrdersHead.rawCollection().aggregate([
      { $match }
    ]).toArray();
    console.log(orderedItems);
    return orderedItems;
  } catch (err) {
    throw new Meteor.Error('E1234', err.message);
  }
}
1 Like

Thanks a bunch @robfallows, your post guided me in the right direction! :smiley:

1 Like

Boom! Thanks so much. Need to dig into the promises stuff more.

1 Like