blaineh
blaineh

Reputation: 2323

Meteor, Cursor not staying in sync with Collection

I'm using some form inputs to create a new Student with the following code:

var student_id = Students.insert({firstname: firstInput, lastname: lastInput, price: priceInput});
Meteor.users.update({_id: Meteor.userId()}, {$push: {'student_ids': student_id}});

I have the following subscriptions and publications set up:

// On the client.
Meteor.subscribe('currentUser');

// On the server.
// I know this is ugly, but I need to do quite a bit of joining.
Meteor.publish('currentUser', function() {
    if (!this.userId) return;
    var userCursor = Meteor.users.find({_id: this.userId}, { fields: {firstname: true, lastname: true, student_id: true, student_ids: true, payment_ids: true, phones: true }});
    var user = userCursor.fetch()[0];
    if (user.student_ids || user.payment_ids) {
        var student_ids = user.student_ids || [];
        var studentCursor = Students.find({_id: {$in: student_ids}});

        var payment_ids = user.customer.payment_ids || [];
        var paymentCursor = Payments.find({_id: {$in: payment_ids}});

        var lesson_ids = [];
        var expense_ids = [];
        studentCursor.forEach(function(doc) {
            lesson_ids.concat(doc.lesson_ids);
            expense_ids.concat(doc.expense_ids);
        });
        var lessonCursor = Lessons.find({_id: {$in: lesson_ids}});
        var expenseCursor = Expenses.find({_id: {$in: expense_ids}});

        return [userCursor, studentCursor, lessonCursor, expenseCursor, paymentCursor];
    }
    else return userCursor;
});

The problem is that one of my {{#each}} blocks is listing all of these students, and it works fine, except that it doesn't show the new student until the page is refreshed/restarted etc. The publish/subscribe pair isn't being reactive.

I'm not sure how to elegantly solve this problem. I definitely would prefer not to mess with added and other such callbacks in the publish function. It seems my collections should handle this behavior on their own.

Thanks in advance!

Update

I changed my publish to use publish-with-relations, a reactive join package, and it now looks like this:

Meteor.publish('currentUser', function() {
    var studentMappings = [{
        key: 'lesson_ids',
        collection: Lessons,
    },{
        key: 'expense_ids',
        collection: Expenses,
    }];

    return Meteor.publishWithRelations({
        handle: this,
        collection: Meteor.users,
        filter: this.userId,
        options: { fields: {firstname: true, lastname: true, student_id: true, student_ids: true, payment_ids: true, phones: true }},
        mappings: [{
            key: 'student_id',
            collection: Students,
            mappings: studentMappings
        },{
            key: 'student_ids',
            collection: Students,
            mappings: studentMappings
        },{
            key: 'payment_ids',
            collection: Payments,
        }]
    });
});

Everything gets published, but it still isn't reactive! When the page is freshly reloaded, everything is there as expected, but immediately after adding a new student, there is only a flicker where that student would be (I suspect this is latency compensation at work).

When I query Meteor.user() in the console, the student_ids array is correct:

student_ids: Array[1]
    0: "5dafpCD7XGcBnyjWd"
  length: 1

and when I meteor mongo this:

meteor:PRIMARY> db.students.find()
{ "firstname" : "Sterling", "lastname" : "Archer", "price" : "22.50", "_id" : "5dafpCD7XGcBnyjWd" }

everything is also correct, but still the document doesn't show up until a page refresh.

Wasn't publish-with-relations supposed to solve this problem?

Upvotes: 1

Views: 232

Answers (1)

David Weldon
David Weldon

Reputation: 64312

Publish functions are not reactive. Even if the user document changes, the publish will not rerun and send the student documents to the client. What you need is a reactive join. See this answer for a practical solution, and this post for further enlightenment.

Update

Based on the comments below, here is a suggestion for how to trigger a publication based on changes to the user. Note that you don't actually have to do anything with student_ids in the publish function - it's there just to trigger subscription on a change to that key.

Tracker.autorun(function() {
  if (Meteor.user()) {
    Meteor.subscribe('students', Meteor.user().student_ids);
  }
});

Upvotes: 2

Related Questions