1

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?

blaineh
  • 2,263
  • 3
  • 28
  • 46

1 Answers1

2

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);
  }
});
Community
  • 1
  • 1
David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • I don't see anything obviously wrong with your PWR code. If there are no other publishers that send the same data to the client, and the data is there when the client starts, then that's a good sign. Could the problem be something simple like an allow/deny rule? When you add the new student, do you see him/her in the user's student_ids array when you inspect the database directly (`$ meteor mongo`)? – David Weldon Feb 19 '14 at 19:49
  • I do. The student is in the database properly, and the id is correctly in the `student_ids` array. If I refresh the page right after adding the student, everything shows up properly, which I think shows that the publish works as long as it's run again. Could this have something to do with the way I installed the package? – blaineh Feb 19 '14 at 19:54
  • 1
    I doubt it's a package installation issue. I'll see if I can reproduce this on my end with the code you have given. I need to finish a project first - give me an hour or two. – David Weldon Feb 19 '14 at 20:01
  • No problem, I appreciate your help too much to be quite that demanding! haha – blaineh Feb 19 '14 at 20:02
  • Okay, so there are a few problems. One is that you used `student_ids` as a key, but they actual key is `customer.student_ids`. I found I couldn't use a nested key like that in PWR so I just put the `student_ids` on the user object... and found that it wasn't reactive. I guess this is a [known issue](https://github.com/erundook/meteor-publish-with-relations/issues/9). There are still a bunch of ways to solve this problem, but to use PWR I think you'd need to reverse the keys and add an `owner` (or something) to the student documents. – David Weldon Feb 19 '14 at 22:50
  • Alternatively if you want to keep `student_ids` on the user you could add a `Deps.autorun` on the client which watches for changes to the user and then subscribes for those ids to a different publish function. Again, there are lots of ways to do this. – David Weldon Feb 19 '14 at 22:53
  • I just manually retriggered the subscription by calling `Meteor.subscribe()` right after the insert. Definitely a hack. – blaineh Feb 20 '14 at 16:13
  • What I'd really like to do is fix the known issue! How doable do you think that is? – blaineh Feb 20 '14 at 16:14
  • 1
    I added a suggestion to my answer which should be better than manually calling `subscribe` after the insert (yuck). As for PWR, you can try to fix the bug in a fork. I have been strongly lobbying the core devs to have a join library in 1.0, so hopefully that will be an improved alternative. – David Weldon Feb 20 '14 at 18:52