0

I'm coding the notification system (like facebook notification). So I have a notifications collection, each document in which has an 'actorId' field that stores the _id of the user who invokes that notification. I want to publish newest notifications AND actors' info of those notifications in a single publication. So this is my publish function:

Meteor.publish("myNotifications", function () {
    let notificationCursor = Notifications.find({receiver: this.userId});

    // get an array of actorIds, so we can fetch users' info in a single query
    let actorIds = [];
    notificationCursor.forEach(function(notification) {
        actorIds.push(notification.actor);
    });

    return [
        notificationCursor,
        Meteor.users.find({_id: { $in: actorIds }})
    ];
});

I use React. This is my component:

NotificationBlock = React.createClass({
    mixins: [ReactMeteorData],
    getMeteorData() {
        let data = {
            notifications: []
        };
        let handle = Meteor.subscribe('myNotifications');
        if (handle.ready()) {
            data.notifications = Notifications.find({}).fetch();
        }
        return data;
    },
    renderNotifications() {
        let list = [];
        _.each(this.data.notifications, notification => {
            let actor = Meteor.users.findOne(notification.actorId);
            list.push(
                <li key={notification._id}>
                    {actor.profile.name} did something...
                </li>
            );
        });
        return list;
    },
    render() {
        return (
            <ul>
                {this.renderNotifications()}
            </ul>
        );
    }
});

The problem is, when there is a new notification, it seems only that notification get published through 'myNotifications' publication. The actor info of that new notification does not get through. So client console shows an error saying that 'actor' is undefined on this line in the react component:

{actor.profile.name} did something...

But if I refresh the browser, the new notification shows up properly (with actor's info) without any error in the console at all!

My guess is that when publishing multiple cursors in a single publication, only cursors of those collections that have 'added', 'changed', 'removed' events are updated, right? That's why "Meteor.users.find({_id: { $in: actorIds }})" does not include new user info, although its params were changed.

Tacaza
  • 539
  • 1
  • 4
  • 12
  • 1
    Also see [common mistakes](http://dweldon.silvrback.com/common-mistakes) for some other issues here, like publishing unfiltered users. – David Weldon Feb 22 '16 at 22:31

1 Answers1

1

The classic solution to this kind of collection joining problem is to use the reywood:publish-composite package. With that your publication would become:

Meteor.publishComposite('myNotifications', {
  find: function() {
    return Notifications.find({receiver: this.userId});
  },
  children: [
    {
      find: function(notification) {
        return Meteor.users.find(
          { _id: notification.actor },
          { fields: { profile: 1 } });
      }
    }
  ]
});

Note also the limitation of the fields returned from the users collection. You really don't want to return the whole user object for each other user.

Michel Floyd
  • 18,793
  • 4
  • 24
  • 39
  • Thanks for pointing to the package, although it's not as optimized b/c you have to run a separate query to get user info for each notification. I do find that the issue is already well-known, and written in https://www.discovermeteor.com/blog/reactive-joins-in-meteor/ . By the way I do limit fields returned from users collection, but cut out from the code in the question to simplify it. – Tacaza Feb 23 '16 at 05:01