How Does the Messages-Count Example in Meteor Docs Work

How does the messages-count example in Meteor docs work?

Thanks for prompting me to write a clearer explanation. Here's a fuller example with my comments. There were a few bugs and inconsistencies that I've cleaned up. Next docs release will use this.

Meteor.publish is quite flexible. It's not limited to publishing existing MongoDB collections to the client: we can publish anything we want. Specifically, Meteor.publish defines a set of documents that a client can subscribe to. Each document belongs to some collection name (a string), has a unique _id field, and then has some set of JSON attributes. As the documents in the set change, the server will send the changes down to each subscribed client, keeping the client up to date.

We're going to define a document set here, called "counts-by-room", that contains a single document in a collection named "counts". The document will have two fields: a roomId with the ID of a room, and count: the total number of messages in that room. There is no real MongoDB collection named counts. This is just the name of the collection that our Meteor server will be sending down to the client, and storing in a client-side collection named counts.

To do this, our publish function takes a roomId parameter that will come from the client, and observes a query of all Messages (defined elsewhere) in that room. We can use the more efficient observeChanges form of observing a query here since we won't need the full document, just the knowledge that a new one was added or removed. Anytime a new message is added with the roomId we're interested in, our callback increments the internal count, and then publishes a new document to the client with that updated total. And when a message is removed, it decrements the count and sends the client the update.

When we first call observeChanges, some number of added callbacks will run right away, for each message that already exists. Then future changes will fire whenever messages are added or removed.

Our publish function also registers an onStop handler to clean up when the client unsubscribes (either manually, or on disconnect). This handler removes the attributes from the client and tears down the running observeChanges.

A publish function runs each time a new client subscribes to "counts-by-room", so each client will have an observeChanges running on its behalf.

// server: publish the current size of a collection
Meteor.publish("counts-by-room", function (roomId) {
var self = this;
var count = 0;
var initializing = true;

var handle = Messages.find({room_id: roomId}).observeChanges({
added: function (doc, idx) {
count++;
if (!initializing)
self.changed("counts", roomId, {count: count}); // "counts" is the published collection name
},
removed: function (doc, idx) {
count--;
self.changed("counts", roomId, {count: count}); // same published collection, "counts"
}
// don't care about moved or changed
});

initializing = false;

// publish the initial count. `observeChanges` guaranteed not to return
// until the initial set of `added` callbacks have run, so the `count`
// variable is up to date.
self.added("counts", roomId, {count: count});

// and signal that the initial document set is now available on the client
self.ready();

// turn off observe when client unsubscribes
self.onStop(function () {
handle.stop();
});
});

Now, on the client, we can treat this just like a typical Meteor subscription. First, we need a Mongo.Collection that will hold our calculated counts document. Since the server is publishing into a collection named "counts", we pass "counts" as the argument to the Mongo.Collection constructor.

// client: declare collection to hold count object
Counts = new Mongo.Collection("counts");

Then we can subscribe. (You can actually subscribe before declaring the collection: Meteor will queue the incoming updates until there's a place to put them.) The name of the subscription is "counts-by-room", and it takes one argument: the current room's ID. I've wrapped this inside Deps.autorun so that as Session.get('roomId') changes, the client will automatically unsubscribe from the old room's count and resubscribe to the new room's count.

// client: autosubscribe to the count for the current room
Tracker.autorun(function () {
Meteor.subscribe("counts-by-room", Session.get("roomId"));
});

Finally, we've got the document in Counts and we can use it just like any other Mongo collection on the client. Any template that references this data will automatically redraw whenever the server sends a new count.

// client: use the new collection
console.log("Current room has " + Counts.findOne().count + " messages.");

How do you adapt the Meteor message count example to track mulitple collections?

You can access counts on client side in this way:

    Deps.autorun(function () {
console.log('inside autorun');
Meteor.subscribe("counts-all");
console.log(Counts.find({_id:"persons"}).fetch());
var persons = Counts.findOne({_id:"persons"}) || {};
var sources = Counts.findOne({_id:"sources"}) || {} ;
var families = Counts.findOne({_id:"families"}) || {};
console.log(persons.count, sources.count, families.count);
});

Mongo.collection vs Meteor.collection and Deps -> Tracker changes were introduced in v0.9.1.

Meteor subscribe to a count

I have following code to publish my counters

Meteor.publishCounter = (params) ->
count = 0
init = true
id = Random.id()
pub = params.handle
collection = params.collection
handle = collection.find(params.filter, params.options).observeChanges
added: =>
count++
pub.changed(params.name, id, {count: count}) unless init
removed: =>
count--
pub.changed(params.name, id, {count: count}) unless init
init = false
pub.added params.name, id, {count: count}
pub.ready()
pub.onStop -> handle.stop()

and I use it like this:

  Meteor.publish 'bikes-count', (params = {}) ->
Meteor.publishCounter
handle: this
name: 'bikes-count'
collection: Bikes
filter: params

and finally on client:

Meteor.subscribe 'bikes-count'
BikesCount = new Meteor.collection 'bikes-count'

Template.counter.count = -> BikesCount.findOne().count

Meteor: Maintain live counts for different views of collection

I think you have two options.

The easier option (which is perhaps not the most efficient) is to subscribe to all the different queries you are interested in:

 _.each(queries, function(query) {
Meteor.subscribe('messages', query);
})

And when you need the messages, use the same query to select only the ones you are interested in:

 Template.queryView.messages = function() { 
return Messages.find(queries[Session.get('currentQueryNo')]);
}

Obviously, you can then do the counts you need.

The other alternative would be to publish a special counts collection, which is manually setup. First, do something very similar to the counts-by-room example in the publishing section of the docs, then client side, do something like:

 var Counts = new Meteor.Collection('counts');
_.each(queries, function(query) {
Meteor.subscribe('counts-by-query', query);
})

Each subscription to counts-by-query will maintain one record in the counts collection.

Hope that helps!

Meteor Collections.count() too slow on UI

This is a good use case for a universal publication, i.e. one that is automatically sent to all clients.

Server:

stats = new Mongo.collection('stats'); // define a new collection
function upsertVoterCount(){
stats.upsert('numberOfVoters',{ numberOfVoters: voters.find().count() });
}
upsertVoterCount();

Meteor.publish(null,function(){ // null name means send to all clients
return stats.find();
});

var voterCursor = voters.find();
voterCursor.observe({
added: upsertVoterCount,
removed: upsertVoterCount
});

Then on the client you can get the voter count anytime with:

var nVoters = stats.findOne('numberOfVoters').numberOfVoters;

Meteor counts-by-room example causing template not to load

I just tried recreating the minimum amount required of your example to get it to work and I don't have the same issue as you.

Do you have the javascript console open in your browser? I would look for an error there, usually this kind of stuff happens when a helper is called upon when the collection data isn't available to the client. This is what iron-router's waitOn fixes which you have made use of.

In my reproduction I only have the one subscription (counts-by-entry) so maybe there is a issue with the other ones.

As for what the initializing part does:

The publish block is a piece of code that will be run for each client subscription. It does 2 things, it provides the client with the initial payload of data which in a traditional publication would be all the documents from a collection query then it reacts to changes that affects the result of the query and sends just those changes to the client.

Here is the most common publication you will see:

Meteor.publish("all-reviews", function() {
return Reviews.find({});
});

Meteor hides the complexities of what is really going on in this publication. This is closer to what is really going on:

Meteor.publish("all-reviews", function() {
var self = this;

//this is the query we want realtime updates for
//when relevant additions, removals or changes happen the correct callbacks will fire...
var handle = Reviews.find({}).observeChanges({
added: function(id, fields) {
//when a document is added it gets sent to the client.
//Note: the initial payload of data happens here, lets say you had 5 reviews
//this would get called 5 times as soon as a user subscribes.
self.added("reviews", id, fields);
},
removed: function(id) {
//when a document is removed the client is told which one
self.removed("reviews", id);
},
changed: function(id, fields) {
//when a document has a change made to its fields the changes get sent
self.changed("reviews", id, fields);
}
});

//letting the client know that the initial payload of data has been sent.
//Stuff like iron-routers waitOn would rely on this message before loading a template
//that relies on this publication
self.ready();

//stops the publication running forever. This will fire when a client no longer needs a
//subscription or when they close the page.
self.onStop(function() {
handle.stop();
});
});

As for what it going on in the docs example with the initializing flag. The initializing flag is used as a way of simply counting all the initial payload of existing reviews in your case then after the observeChanges call telling the client how many there are. This is an optimisation on the other way of doing it which would be to send the client several self.changed messages during the initial payload.

Maybe it will make more sense if I show how it would be done without the flag:

Meteor.publish("counts-by-entry", function (entryId) {
var self = this;
check(entryId, String);
var count = 0;
//we need to initially add the document we are going to increment
self.added("counts", entryId, {count: 0});
var handle = Reviews.find({entry: entryId}).observeChanges({
added: function (id) {
count++;
//So for example if there were 100 reviews for this entry when the user
//subscribes this self.changed would get called 100 times:
//self.changed("counts", entryId, {count: 1})
//self.changed("counts", entryId, {count: 2})
//self.changed("counts", entryId, {count: 3})
//self.changed("counts", entryId, {count: 4}) etc...
//which is a waste when we can just initially tell the client how
//many reviews there are at the point of them subscribing
self.changed("counts", entryId, {count: count});
},
removed: function (id) {
count--;
self.changed("counts", entryId, {count: count});
}
});
//remove the self.added(...) which handles the optimiation as explained above
self.ready();
self.onStop(function () {
handle.stop();
});
});

Regardless it doesn't look like that particular publish is the problem. I would expect the console to make it clear what the issue is

Confused about Meteor: how to send data to all clients without writing to the database?

At the moment there isn't an official way to send data to clients without writing it to a collection. Its a little tricker in meteor because the step to send data to multiple clients when there isn't a place to write to comes from when multiple meteor's are used together. I.e items sent from one meteor won't come to clients subscribed on the other.

There is a temporary solution using Meteor Streams (http://meteorhacks.com/introducing-meteor-streams.html) that can let you do what you want without writing to the database in the meanwhile.

There is also a pretty extensive discussion about this on meteor-talk (https://groups.google.com/forum/#!topic/meteor-talk/Ze9U9lEozzE) if you want to understand some of the technical details. This will actually become possible when the linker branch is merged into master, for a single server

Here's a bit of way to have a 'virtual collection, its not perfect but it can do until Meteor has a more polished way of having it done.

Meteor.publish("virtual_collection", function() {
this.added("virtual_coll", "some_id_of_doc", {key: "value"});

//When done
this.ready()
});

Then subscribe to this on the client:

var Virt_Collection = new Meteor.Collection("virtual_coll");
Meteor.subscribe("virtual_collection");

Then you could run this when the subscription is complete:

Virt_Collection.findOne();
=> { _id: "some_id_of_doc", key: "value"}

This is a bit messy but you could also hook into it to update or remove collections. At least this way though you won't be using any plugins or packages.

See : https://www.eventedmind.com/posts/meteor-how-to-publish-to-a-client-only-collection for more details and a video example.



Related Topics



Leave a reply



Submit