Mongodb aggregate


#1

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.


#2

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

#3

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


#4

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.


#5

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

#6

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


#7

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.


#8

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


#9

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???


#10

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

#11

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


#12

thanks for your explain.


#13

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

#14
  1. what’s the error?
  2. you could also pass it as param to your aggregate method

#15

Get error, when I fixture data on startup

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

#16

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.