3

I am migrating from a custom user rights management system to Alanning:roles v2.0. I have a very basic structure:

  • A basic user
  • Groups of users, each with specific settings. I store them in a "group" collection.
  • A group admin status for users who manage the groups (each group has its group admins).

I was previously storing the group members and admins mongo _id in the "group" document. This way, I could publish the groups reactively : I just had to check if the userId was in the group document, in the "members" or "admins" fields.

Now that I switched to a right management enforced by Alanning:roles, I do something like this in my publication :

const userGroupsAsAdmin = Roles.getPartitionsForUser (this.userId, ['group_admin'])
const userGroupsAsMember = Roles.getPartitionsForUser (this.userId, ['member'])
const selector = {$or:[{'_id':{$in: userGroupsAsMember}},{'_id':{$in: userGroupsAsAdmin}}]}
const options = {}
const response = Groups.find(selector, options)
return response

Note that Roles.getPartitionsForUser () is just the new function name for Roles.getGroupsForUser ().

The problem here is that the publication don't watch for changes in the role collection, so when a user becomes member, the publication isn't updated. I know this is a common issue and I know 3 ways to fix this, but none of them feels satisfying:

  • The best candidate: denormalize and duplicate. I keep my members and admins fields in the group document. What bugs me is that I will keep 2 versions of the same thing and create a possibility for inconsistencies to appear.

  • Add an argument to the publication and rerun it using this argument (e.g. userGroupsAsMember) but it relies on client and makes it send unnecessary info.

  • Use low level publication api, either directly or using a package. I already did this directly in the past but I don't want to rely on Cursor.observe() anymore because it doesn't scale efficiently and create an unnecessary server load.

Am I missing an option? If not, what would be the best way to keep my publication reactive?

Billybobbonnet
  • 3,156
  • 4
  • 23
  • 49

1 Answers1

2

Use reywood:publish-composite to create a reactive join.

Meteor.publishComposite("my-groups", {
  find: function() {
      // findOne won't work, it's not a cursor
      return Meteor.users.find(
        {_id: this.userId},
        {fields: {roles: 1}}
      );
    },
  children: [{
    find: function(user) {
      // getPartitionsForUser allows the first parameter to be the actual user object.
      const userGroupsAsAdmin = Roles.getPartitionsForUser (user, ['group_admin']);
      const userGroupsAsMember = Roles.getPartitionsForUser (user, ['member']);
      const selector = {$or:[{'_id':{$in: userGroupsAsMember}},{'_id':{$in: userGroupsAsAdmin}}]};
      const options = {};
      const response = Groups.find(selector, options);
      return response;
    }
  }]
});

Roles.getPartitionsForUser doesn't return a cursor, therefore it can't be reactive. That's why you'll need to publish a Meteor.users.find call, too.

aedm
  • 5,596
  • 2
  • 32
  • 36
  • Thank you for your answer. This would match my third option, i.e. using `Cursor.observe()`. This is imho the less desirable option because each server will have to observe, and it will end up as a big load (as opposed to none for the other 2 options) if I have many users online. Any reason why you would prefer this over the 2 other? Note that my app is supposed to work at a rather large scale. – Billybobbonnet Apr 26 '16 at 13:48
  • I'd guess observing a cursor in this case wouldn't result in a huge load, since the Meteor.users doesn't change often, and the relevant parts are kept in memory anyway. Observing `Groups` would use up more resources, I assume. But that's obviously only a hunch without solid evidence. – aedm Apr 26 '16 at 14:05
  • I wish I could find a real life data based comparison of a denormalized vs a cursor observe approach. – Billybobbonnet Apr 26 '16 at 14:38
  • 1
    You need to observe changes because of your requirements. Your options 2 and 3 will have more or less the same effect. Option 3 is indeed better, as it avoids round trip. This should be handled pretty efficiently using the low level publication API (make sure you have Oplog enabled though). Also,I'm guessing your users roles will probably not change at a high frequency. And "denormalize and duplicate" is probably something you'll have to do in the future, that's because Mongo (even though I would avoid it here personally), so you may want to get comfortable with the idea :) – Julien Apr 26 '16 at 16:01