This.ready in a publication?


#1

I’m trying to understand how this.ready() works in a publication. Per the docs it’s explained that this.ready():

Informs the subscriber that an initial, complete snapshot of the record set has been sent. This will trigger a call on the client to the onReady callback passed to Meteor.subscribe, if any.

However, in the past I’ve defined publications without this.ready() and have had them work fine. What I’m reading above is that my onReady callback on my subscription will not fire unless this.ready() is called. Is that correct? The bigger question I’m trying to answer is: how necessary is this.ready() and does not having it introduce any specific problems? Can anybody explain this in a more practical sense?

Here’s an example publication where I’m using this.ready():

Meteor.publish('singlePost', function(slug){
  check(slug, String);
  var data = Posts.find({"slug": slug});
  if (data) {
    return data;
  }
  return this.ready();
});

#2

Great question. I’ve seen code returning an empty array instead of returning this.ready() and would like to know if there’s any difference between them?


#3

Taken from http://docs.meteor.com/#/full/meteor_publish

// server: sometimes publish a query, sometimes publish nothing
Meteor.publish("secretData", function () {
  if (this.userId === 'superuser') {
    return SecretData.find();
  } else {
    // Declare that no data is being published. If you leave this line
    // out, Meteor will never consider the subscription ready because
    // it thinks you're using the added/changed/removed interface where
    // you have to explicitly call this.ready().
    return [];
  }
});

#4

This one if the ugliest part of meteor. They have the same effect.

Returning [] means you send an array of cursors. But nothing in it. Then meteor will call this.ready() after processing cursors. In this case none.

On the other way, you can directly call this.ready()

But, I prefer to use this.ready() over [].


#5

+1 for this.ready(). Calling this.ready() means that all the data is fetched on the server side and no other actions needed. It many cases it could make subscritions faster


#6

Faster? In what sense? Do you mean something like “send the first 10 docs, mark the subscription as ready and then send the remaining docs”?


#7

This is more to do with “Meteor … thinks you’re using the added/changed/removed interface …” (see the annotation in @serkandurusoy’s reply).

Generating your own publication data this way means you have to use this.ready() to signal completion of the publication to the subscribers.

I highly recommend @manuel’s video: https://www.youtube.com/watch?v=RmjYw4G4ImQ which explains this really well, and demonstrates a great use for speeding up subscriptions (it’s towards the end of the video).


#8

Until now, I’ve been handling such cases like this:

Meteor.publish("secretData", function () {
  if (this.userId === 'superuser') {
    // If the user has access, just publish the data
    return SecretData.find();
  } else {
    // Otherwise publish a set you know to be empty
    return SecretData.find({_id: -1});
  }
});

Other than performance issues, are there any known drawbacks on this?


#9

In that case I would use this.error instead of return SecretData.find({_id: -1}). This way, you get self documented code.


#10

There’s a lot of confusion going on in this thread. Let’s take it piece by piece…

this.ready() is what tells the client that the publication is “initially done” or better yet “done for now” (the server can send more documents later on). In the following example the client will never know when the server is done sending the initial batch of documents. That’s because even though the server has finished sending the documents, it’s not sending the ready notification.

// Server
Meteor.publish("people", function () {
  this.added("people", "1", { name: "Alan" });
  this.added("people", "2", { name: "Brito" });
  // Not sending the ready event!
});

// Client
Template.body.onCreated(function () {
  this.subscribe("people", function () {
    console.log("This will NOT print.");
  });
});

If we add this.ready() the client then knows when the server is done sending the initial batch:

// Server
Meteor.publish("people", function () {
  this.added("people", "1", { name: "Alan" });
  this.added("people", "2", { name: "Brito" });
  this.ready();
});

// Client
Template.body.onCreated(function () {
  this.subscribe("people", function () {
    console.log("This WILL print");
  });
});

Now regarding the following statements:

this.ready();
return [];
return People.find( { "_id": "-1" });

They all send the ready notification to the client but for different reasons.

this.ready() is the most straightforward and it just sends the ready notification to the client.

When you return an array (in a publication), Meteor loops through the array of cursors and sends an added document to the client for each element in the cursors. When it’s done it then sends the ready event to the client. That’s why return []; does sends the ready notification, because it loops through the empty array, it doesn’t send any records to the client and then fires the ready event.

When you return a cursor, Meteor goes to the database, fetches the records, sends an added document for each record, and then sends the ready notification. return People.find( { "_id": "-1" }); sends the ready notification because Meteor goes to the database, doesn’t find/send any records, and then fires the ready event.

So the following two snippets are equivalent (Up to the ready point because the first one will keep sending updates to the client while the second one doesn’t send anything else):

Meteor.publish("people", function () {
  return People.find();
});

===

Meteor.publish("people", function () {
  var self = this;
  People.find().forEach(function (person) {
    self.added( "people", person._id, person );
  });
  self.ready();
});

And thus this.ready() is better than return []; which is better than return People.find( { "_id": "-1" });

Regarding the performance optimization I used at the end of my presentation (https://www.youtube.com/watch?v=RmjYw4G4ImQ). That only applies when the publication returns an aggregate (in the presentation we do a reactive aggregate). Without the initializing variable the server would send one “aggregated” document for each element in the array.

For example, if there are 50 names and they all start with “A”, without using the initializing optimization, the server would loop through the collection, pick up the first name and send { letter: "A", count: 1 }, then pick the second and send { letter: "A", count: 2 }, and so on. With the optimization the server performs the aggregate and then sends the final count once: { letter: "A", count: 50 }

As for executing this.ready() vs throwing an error when you know the collection is empty, it depends. If the client isn’t supposed to subscribe to the publication then you should throw an error. If you want the client to subscribe to a publication and let the server decide what to send back, then use this.ready().

I hope this helps.


#11

The key point here is that when publishing a cursor, Meteor sends the DDP messages, including ‘ready’ automatically.


#12

Yes, thats it! But it depends on how complicated subscribtion is. It is a nice way when you know that subsciption’s initial subset is really huge (>200 docs).


#13

Awesome answer. Thanks, Manuel!


#14

@manuel Awesome answer! I’m still curious of that

means.
Is this.ready() invoked again after the initial load ?


#15

Not unless you subscribe again.


#16

Oh Thank you very much!


#17

One question, just for clarification: I noticed that even if I block access to a page for a non-registered user via the useraccounts:flow-routing package, my user-related subscriptions will fire and I get an error because the userId is not available yet and hence my query to the user collection returns nothing. This even happens if an auto-login takes place, so it is just a matter of milliseconds that the subscription fires before the auto-login has taken place.

Is it safe in this case to just return this.ready() from the subscription? In other words: will the subscription still automatically be re-run, if the auto-login eventually succeeds? I am subscribing in an this.autorun() inside the onCreated() of the template that is shielded against public access.


#18
  1. Publish functions are called both:
  • when you call the corresponding subscribe, and
  • when the user id changes (from undefined to a value, from a value to undefined, from a value to another value).
  1. I think it is always safe to call this.ready() in a publish function. And this has nothing to do with point 1 above.

#19

Thanks for your quick reply!


#20

1 more thing from examples above - you dont return this.ready();, you call it.
At least I never tried it and not planning on it :smiley: