0

For instance, when your permissions are group-based, and your user document has a list of groups that the user belongs to. I'm publishing docs in an Items collection, and you should only be able to view items with a groupOwner field matching a group you belong to.

It would be nice if you could autorun inside a publish, but I doubt you can:

Meteor.publish 'screened-items', ->
  Deps.autorun ->
    user = Users.findOne @userId
    return Items.find {groupOwner: {$in: user.groups}}

If you can't, this is the best I can come up with, but it's going to be slow and memory-intensive. Is this the only way to do it?

Meteor.publish 'screened-items', ->
  user = Users.findOne @userId

  # (hope that the db doesn't change between this line and the observeChanges)
  saved_items = Items.find({groupOwner: {$in: user.groups}}).fetch()
  # call @added on each item

  handle = Users.findOne(@userId).observeChanges {
    changed: (_, fields) =>
      if fields.groups
        new_items = Items.find({groupOwner: {$in: fields.groups}}).fetch()
        # compare new_items to saved_items, and call @added() or @removed() for each difference
  }

  @ready()
  @.onStop ->
    handle.stop()
Loren
  • 13,903
  • 8
  • 48
  • 79
  • Why would the first one not work? Have you tried it? – Christian Fritz Jan 29 '14 at 01:28
  • I haven't. I will if someone doesn't have a quick answer. In order for autorun to work, wouldn't Meteor need to put its use of what I return in their own autorun, treating what I return as a reactive data source? – Loren Jan 29 '14 at 03:22

2 Answers2

1

You can achieve this two ways:

  1. Use the publish-with-relations package, for example:

    Meteor.publish 'screend-items', ->
      # select the current user
      Meteor.publishWithRelations
        handle: this
        collection: Meteor.users
        filter:
          _id: @userId
        options:
          fields:
            groups: 1
        mappings: [
          key: 'groupOwner'  # and map to the `groupOwner` field on Items
          collection: Items
        ]
    
  2. Denormalize the relationship, providing a succinct list of users to use for publishing

    Items._ensureIndex(userIds: 1) # best to index this field
    # basic publications
    Meteor.publish 'screend-items', ->
      # don't expose `userIds` to the client
      return Items.find({userIds: @userId}, {fields: userIds: false})
    
nathan-m
  • 8,875
  • 2
  • 18
  • 29
1

If you want the published docs to change when the userId changes, that is the default behaviour.

However, if the logged-in user changes, the publish function is rerun with the new value. - from docs.meteor.com.

Deps.autorun() only works on the client while Meteor.publish() only works on the server. So you can not autorun inside of publish.

If you are okay to let the client see the 'groups' they're in, the code is a bit simpler because you can start and stop the subscription when the groups change. Like this:

//on client 
Deps.autorun( function() {
  Meteor.subscribe( 'items', Meteor.user().groups );
});

//on server
Meteor.publish( 'items', function( groups ){
  var self = this;
  var user = Meteor.users.findOne( {_id: self.userId});

  if ( ! (user && user.groups === groups) )
    return;

  return Items.find({groupOwner: {$in: groups}});
});

Otherwise you would need use two observers inside the publish function - one to watch user for changes to groups and another to manage publishing items that are in the group. See this example of doing a join of collections that way.

Community
  • 1
  • 1
user728291
  • 4,138
  • 1
  • 23
  • 26
  • I don't trust the client to tell the server which group it is in. Two observers sounds better than my second solution. – Loren Jan 29 '14 at 04:21
  • You don't have to trust the user. The publish example I provided checks that the user provided info is exactly equal to what comes from the db. Having the user provide it is just a way to trigger the rerun of the publish. – user728291 Jan 29 '14 at 05:08