Subscriptions readiness in server publications


#1

Pub-sub are pretty powerful but hard to understand.
I have the following situation which makes my application crashing.

I have 2 collections:

Profiles, which I subscribe and wait for in the router.js.
WorkJournals which I subscribe at template level.

I have a crash in the publication module at sign out time. Here here my code:

Meteor.publish('profiles', function() {
    return Profiles.find();

and

Meteor.publish('workjournals', function() {
    var p = Profiles.findOne({userId : this.userId});
    return WorkJournals.find({companyId: p.companyId});
});

When I logout from the application – I use the standard accounts-password and accounts-ui packages – I got the following error:

Exception from sub workjournals id FLjAKWZ5uEWfJXQ7s TypeError: Cannot read property 'companyId' of undefined
I20150709-16:51:53.676(2)?     at [object Object]._handler (app/server/publications.js:122:31)
I20150709-16:51:53.678(2)?     at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1617:1)
I20150709-16:51:53.678(2)?     at [object Object]._.extend._runHandler (packages/ddp/livedata_server.js:950:1)
I20150709-16:51:53.678(2)?     at [object Object]._.extend._startSubscription (packages/ddp/livedata_server.js:769:1)
I20150709-16:51:53.679(2)?     at [object Object]._.extend.protocol_handlers.sub (packages/ddp/livedata_server.js:582:1)
I20150709-16:51:53.679(2)?     at packages/ddp/livedata_server.js:546:1

Any ideas?
Am I supposed to verify whether the Profiles subscription is ready or not before using it in another publication?

[UPDATE]
I just discovered that companyId is null because this.userId is null, which means that the logout procedure has already triggered.

Now my question is, why does Meteor run publications again?
The landing page after the user sign out is index.html – which in my project and includes just the {{> LoginButtons}} and some static content.


#2

When you log out, all publish functions are executed again with this.userId === null. Hence the error.


#3

Yes, see my update. So do I need to protect publications with something like this:

if (Meteor.userId()) {
...
}

Can I do that?
And why are publications executed again?


#4

yes you can, and publications are executed again because they are invalidated when the data they depend on changes. so when the this.userId changes every subscription that depends on it will run again to make sure you only get to see the allowed data.

see the docs for more info


#5

Meteor.userId() is a reactive data source so when that changes it will cause the publications to rerun. ThaIt’s how meteor stays reactive.


#6

Publications are executed again when user logs in or out. This is a Meteor core feature, which is painful and causes several issues (one example here). It does not relate to “the data [a subscription] depend on” (@davidrums) or “Meteor.userId() being reactive” (@khamoud).


#7

Because Meteor.userId() is a reactive data-source. If it wasn’t how would the publication know to run again?

From the documentation

this.userId
ddp/livedata_server.js, line 912
Access inside the publish function. The id of the logged-in user, or null if no user is logged in.

This is constant. However, if the logged-in user changes, the publish function is rerun with the new value.

Also from the documentation.

Tracker.autorun allows you to run a function that depends on reactive data sources, in such a way that if there are changes to the data later, the function will be rerun.

Last from the documentaion

Meteor.userId()
accounts-base/accounts_client.js, line 11
Get the current user id, or null if no user is logged in. A reactive data source.

Regarding your issue on github, it is a known fact that writing good publications is the key to scaling a meteor app. In your repo for your issue you are subscribing to 10,000 documents which is never a good idea (browsers only have so much space). You are also trying to draw all of them at the same time which is also not a good idea. Writing inefficient code is not a framework problem.


#8

Reactivity is on client only.


#9

Yes that’s true but when the user on the client changes that’s when it sends a message to the server telling to rerun the publications with/without userId. This is necessary because Meteor.userId() is not available inside a publication. Sometimes you don’t want documents published when there is no current user.


#10

I keep having problems on logout.
The fact that this.userId reactivity gets all your publications rerun on logout makes me think that it’s always better to protect each publication with:

if (!this.userId) return this.ready(); //UPDATED

On the other hand, why would you want to run your publication again on logout?
Do you have any alternative pattern to suggest?

This one was suggested here by @cmather


#11

IMO, the fact that Meteor automatically runs publish functions when user logs in or out is a bad idea. This has been discussed here, for example.

For example when you have a collection that logged-in user can see entirely, and not logged-in user can see partially.


#12

I absolutely agree with you @Steve
Is the sequence of logout events documented anywhere?

I found out a couple of other cases where I have to act defensively to avoid unexpected behaviors at logout.