Helper performance and joining two Collections

I’m building an app where users can be part of groups (multiple), and each group includes an invite system. An individual invite can be either pending | accepted | left (the latter meaning the user left the group).

Two relevant Collections on the db side: Invites and Groups.

I’ve got two helpers that seem to be working fine, but I’m seeking to learn if I can be writing better Meteor code. My question is twofold:

  1. Am I hurting performance by using .fetch() in a helper?
  2. Is there a better way to get the data from mongo by somehow using a query that joins the two Collections?

The second question is less important to me (for now) if the answer to the first question is no. Here’s code for two helpers:

acceptedInvites: function(){
    var invites = Invites.find({used: true}, {sort: {createdAt: -1}}).fetch(); // are there invites? 
    if(invites.length){
        var user = Meteor.user();
        if(user){ // wait for reactive source 
            var group = user.profile.group;
            var theGroup = Groups.findOne({_id: group}); // get the current user's group 
            // filter invites that have been accepted. 
            // accepted invites will mean the "members" property (type: array) 
            // on theGroup will *include* the uid assigned to the accepted invite 
            return theGroup && invites.filter(function(doc){ 
                return theGroup.members.includes(doc.uid);
            });
        } else {
            return false;
        }
    } else {
        return false;
    }
},
leftInvites: function(){
    var invites = Invites.find({used: true}, {sort: {createdAt: -1}}).fetch(); // are there invites? (same as above)
    if(invites.length){
        var user = Meteor.user();
        if(user){ // wait for reactive source 
            var group = user.profile.group;
            var theGroup = Groups.findOne({_id: group});
            // except here, we show the invites that were used 
            // but where the user is no longer in the "members" array
            return theGroup && invites.filter(function(doc){
                return !theGroup.members.includes(doc.uid);
            });
        } else {
            return false;
        }
    } else {
        return false;
    }
}

And example data structure:

invites = [
  {
    "_id": "dRJEvcrGX4Aig8Z2g",
    "acceptedAt": "2020-08-11T04:16:14.173Z",
    "createdAt": "2020-08-11T04:14:51.314Z",
    "invitee": "foo@bar.com",
    "inviteeName": "wifey",
    "message": "test",
    "owner": "pjA3gsDEeRDr2wLyo",
    "ownerName": "Mike",
    "uid": "PiwmrTgetr3DceT8b",
    "used": true
  }
];
theGroup = {
  "_id": "F74sRFo2b63wcnWWA",
  "members": [
    "pjA3gsDEeRDr2wLyo",
    "PiwmrTgetr3DceT8b"
  ],
  "startedBy": "pjA3gsDEeRDr2wLyo",
  "createdAt": "2020-08-11T04:14:41.517Z",
  "membersLeft": [
    "PiwmrTgetr3DceT8b"
  ]
}

I understand using .fetch() is less performant because it does not return a cursor that would allow Blaze to re-render only the bits that need re-rendering. But using .fetch() is key here because I want to run a .filter() on the data, which I can’t do with .find() (at least insofar as I’ve researched).

Do the two helpers seem overly complicated and/or non-performant? Should I be doing a cross-Collection check instead (like joining tables in MySQL)? The motivation for asking is that while these may not be performance bottlenecks, at least right now, these helpers play their role in a larger ecosystem of helpers/templates in the app, so I’m wanting to be sure I’m doing things well for a smooth UX.

Thanks for any insight.

If this is client side, everything is done in-memory with no network request, so fetch to your hearts content!

Joins in mongo are complicated at the best of times, and more-so with Meteor as you need to use the raw collection to use aggregations instead of the Meteor collection methods

1 Like

Glad to hear on the fetch front, and that’s what I heard on the join front as well. I know there isn’t a network request given it’s through a socket, but I thought I read that fetch requires the browser to re-render the entirety of the dependent DOM nodes whereas returning a cursor would just modify what was needed.

Ultimately it was CPU performance on the client I worried about but it sounds like it’s not a big deal (which is great).