Meteor subscribe onReady() and observe() added double counted - meteor

I want to wait for all data to be downloaded from the subscription and then create map markers for them all at once at the beginning. To do this, I have a session variable set to false. Then when onReady calls, I initialize all the markers. Then I set the session variable true indicating that the first delivery is in and initialized. In my observe callback, I check the session variable and so long as its false, I dont add any markers. Then, if its true, I will add markers -- assuming non of these markers are already initialized. Sometimes, however, I get a double-count and create twice as many markers.
I guess a good first question to ask is what the relationship is between onReady and observe added? Its not terribly clear in the docs. Is this even the correct way of doing things -- creating a session variable to suppress the observe added function until onReady is done? I dont think so. Also note that the double count doesnt happen every time so its a timing thing... kind of annoying.
Thanks

Yes this is the behavior with observe(). When you run observe initially it will have an initial query that will match everything and run into added.
It is also present when onReady hasn't yet fired but the collections are empty at that point so the initial ones aren't visible. This is mentioned in the docs
Before observe returns, added (or addedAt) will be called zero or more times to deliver the initial results of the query.
I'm not sure entirely how to avoid the initial items. I have done something like this in the past:
var QueryHandle = Collection.find().observe({
added: function() {
if(!QueryHandle) return false;
});
I know this works on the server but I'm not certain if it does on the client.
Another solution would be to run the handle before onReady is called and only stop returning if the subscription is complete: i.e
Meteor.subscribe("collection", function() {
subscribed = true;
});
var QueryHandle = Collection.find().observe({
added: function() {
if(!subscribed) return false;
}
);
Be careful not to run this in a Deps.autorun because then it would be run again if the .find() query params are reactive.
This might happen sometimes depending on how fast the server response. If you use Session it becomes a reactive hash so if it happens fast enough that subscribed returns true. Try using an ordinary variable instead.
If its not helpful there might be an alternative way to avoid the initial ones and a deeper level but it might take a dig into the livedata package.

Related

How can I use collection.find as a result of a meteor method?

I'm trying to follow the "Use the return value of a Meteor method in a template helper" pattern outlined here, except with collections.
Essentially, I've got something like this going:
(server side)
Meteor.methods({
queryTest: function(selector) {
console.log("In server meteor method...");
return MyCollection.find(selector);
}
});
(client side)
Meteor.call('queryTest', {}, function(error, results) {
console.log("in queryTest client callback...");
queryResult = [];
results.forEach(function(result) {
// massage it into something more useful for display
// and append it to queryResult...
});
Session.set("query-result", queryResult);
});
Template.query_test_template.helpers({
query_test_result: function() {
return Session.get("query-result");
}
});
The problem is, my callback (from Meteor.call) doesn't even get invoked.
If I replace the Method with just 'return "foo"' then the callback does get called. Also, if I add a ".fetch()" to the find, it also displays fine (but is no longer reactive, which breaks everything else).
What gives? Why is the callback not being invoked? I feel like I'm really close and just need the right incantation...
If it at all matters: I was doing all the queries on the client side just fine, but want to experiment with the likes of _ensureIndex and do full text searches, which from what I can tell, are basically only available through server-side method calls (and not in mini-mongo on the client).
EDIT
Ok, so I migrated things publish/subscribe, and overall they're working, but when I try to make it so a session value is the selector, it's not working right. Might be a matter of where I put the "subscribe".
So, I have a publish that takes a parameter "selector" (the intent is to pass in mongo selectors).
On the client, I have subscribe like:
Meteor.subscribe('my-collection-query', Session.get("my-collection-query-filter"));
But it has spotty behaviour. On one article, it recommended putting these on Templates.body.onCreate. That works, but doesn't result in something reactive (i.e. when I change that session value on the console, it doesn't change the displayed value).
So, if I follow the advice on another article, it puts the subscribe right in the relevant helper function of the template that calls on that collection. That works great, but if I have MULTIPLE templates calling into that collection, I have to add the subscribe to every single one of them for it to work.
Neither of these seems like the right thing. I think of "subscribing" as "laying down the pipes and just leaving them there to work", but that may be wrong.
I'll keep reading into the docs. Maybe somewhere, the scope of a subscription is properly explained.
You need to publish your data and subscribe to it in your client.
If you did not remove "autopublish" yet, all what you have will automatically be published. So when you query a collection on client (in a helper method for example), you would get results. This package is useful just for quick development and prototyping, but in a real application it should be removed. You should publish your data according to your app's needs and use cases. (Not all users have to see all data in all use cases)

Iron router + observechanges = repeated observechanges handler calls?

I'm attempting to use observechanges with iron router but they don't seem to be compatible at all.
Router.route('/gaming', {
waitOn: function() {
return Meteor.subscribe('chat', function() {
window.chatmessagesCache = new ReactiveVar;
chatmessagesCache.set([]);
return chat.find().observeChanges({
added: function(id, doc) {
var tmpArr;
tmpArr = chatmessagesCache.get();
tmpArr.push(doc);
return chatmessagesCache.set(tmpArr);
}
});
});
}
If I leave the route and come back to it, observechanges begins being handled as many times as I've navigated away and returned, for each new record. What's the deal?
If I use subs manager it works as expected, but I don't understand why Meteor.subscribe inside waitOn is so cache/subscription unaware when it ALREADY gets called multiple times per load. Why!? I can't decipher what's causing this behavior at all.
Also, what I'm trying to accomplish is simple. I want to let chat messages that the user's client has received remain on the page even if the chat cursor is no longer publishing them (I'm publishing the last 10 chat messages)
Iron router has reactivity built in, which means when something inside your route function is invalidated, it will repeat the function as well as anything reactive with a Router.current(). These unexpected invalidation runs are a primary reason why folks made the exodus to flow router.
To solve this, you'll want to abstract your code away from the router. You can leave the sub, but I'd suggest you remove the sub's callback from the waitOn and move it into an onRendered callback. If you don't want the history loaded in chunks, you can first do a var collectionCount = chat.find({},{reactive:false}).count() on how many docs are in the collection & then in the added callback you can do something like if (++currentCount === collectionCount) /* add stuff */ to add al the records to the history when it reaches the last record.
On a bigger picture level, consider eliminating the observeChanges & just do an #each over the chat collection in spacebars to show your messages. Fewer cycles, cleaner code.
Iron router just has no management of observations you created yet it manages subscriptions itself, hence the multiple additions.
I figured this out by using a window level variable to check if I'm observing. Even in cases when the subscription is unhooked by iron, if I go back and never re-add the handler, the original observation hook still runs (!). ALSO, if you navigate away and drop the subscription, the handler is no longer called--which is the behavior I want in this scenario (This is all very insane behavior but at least it's now predictable to me )
This is caused by the fact that subscriptions != collections and the API for observations doesn't seem to expose any metadata, unfortunately, so I don't know how the iron router maintainers would account for this. Not to mention you return iron router a subscription, not a collection.
#Matt K if you were correct, this would always be an infinite loop (which admittedly I had a bunch of while trying to solve this) but the posted code is adding too many handlers, not looping indefinitely. Thanks for the post though.
This is what I settled on
Router.route('/gaming',
waitOn: ->
Meteor.subscribe('chat', ->
window.chatmessagesCache = new ReactiveVar(chat.find().fetch().reverse())
if !window.chatListening
window.chatListening = true
after = chat.find().count()
chat.find().observe(
added: _.after(after + 1,(doc) ->
tmpArr = chatmessagesCache.get()
tmpArr.push(doc)
chatmessagesCache.set(tmpArr))
changed : (id, doc) ->
))
I really just wanted to test out a pattern of locally "disconnected" documents. I still may use subs manager because it keeps subscriptions and their handlers alive without rerunning them constantly (which was rerunning the sub handler, which was adding multiple observations)

When to use the promise returned by ractive.set?

The ractive.set method returns a promise. When performing a simple set operation (single value or map) and then immediately referencing the new value via ractive.get, is it recommended to use the promise? Or is that completely unnecessary?
I've been avoiding the promise and found that I don't need it, but maybe I've just been lucky so far. Here's an example of what I mean:
ractive.set("foo", "bar");
console.log(ractive.get("foo")); // always outputs the correct value "bar"
I'm worried that the set operation is asynchronous and this will become evident on slower machines or if I start using the more advanced features of Ractive.
According to the Ractive docs:
[ractive.set] Returns a Promise that will be called after the set
operation and any transitions are complete.
Based on that, I wonder if the promise is really meant for post-transition work.
Based on that, I wonder if the promise is really meant for
post-transition work.
Exactly. The value update (and the resulting DOM changes per the template) happen synchronously, the promise is meant for asynchronous response to end of transitions.
This is also why the set operation also has a hash map option for the input parameters so multiple sets will be batched in one go:
ractive.set({
foo: 'foo',
bar: 'bar'
}).then( () => {
// this happens asynchronously ***after*** code execution has
// continued below on next event cycle or after transitions complete
});
// data and DOM have been updated as the code continues synchronously here:
console.log( ractive.get() );

Find out all the Queries listening on a Meteor.js Collection

In Meteor 0.7.0.1, is it possible to count/find out the all the queries that are currently listening to a particular Collection?
I am trying to create a function which does: Whenever the number of users listening on a particular query (eg: myCollection.find({color:'red'}) becomes non-zero, execute a function whenever documents are changed/added to a second Collection anotherCollection.
When/how is the find method called? If it's called when someone hits a button on the page, for instance, simply increase a serverside variable that will increase when that happens. To decrease this variable when the user leaves the page, listen to the window.onbeforeunload event and decrease the count when it happens.
Alternately, if you have a login system, assign a boolean value such as online to each user. When they log in, make their online status true with the following code. if(Meteor.user()){Meteor.user().online=true;}. Make sure an onbeforeunload sets their online status to false when they leave. Then, do something like Meteor.users.find({online:true}).size() to get the amount of users online.
Basically, rather than update when myCollection.find({color:'red'}) is called, put that in a function. For instance:
if(Meteor.isClient){
Session.set('browsing', false);
function findColor(c){//Alerts the server when the user attempts to get a color.
//This is presuming they don't use the plain MongoDB command.
//Your button/however you're finding the color should use this command
if(!Session.get('browsing'))//if it's already true, don't increase the count
{
Meteor.call('incBrowsing');
Session.set('browsing', true);
}
return myCollection.find({color:c});
}
window.onbeforeunload = function(){Meteor.call('decBrowsing');};
}
if(Meteor.isServer){
var browsing = 0;
Meteor.methods({
incBrowsing: function(){browsing++;},
decBrowsing: function(){browsing++;}
});
}
I haven't tested this, but I hope it works for you. You didn't provide too many details on your problem, so feel free to comment below if you or I need to clarify something.

Meteor filter collection

I'm trying to secure accessing a specific collection but I'm having troubles doing it. I have no problems disabling the insert, update and delete with the Collection.allow() map.
The problem is that I also want to filter the results returned by the Collection.find() and Collection.findOne() function. I read about the Meteor.publish() and Meteor.subscribe() stuff, but somehow I cannot make it work (it's not getting filtered, I just can see all the results).
In my server-code I do the following:
Groups = new Meteor.Collection("groups");
Meteor.publish("myGroups", function() {
if (Functions.isAdmin(userId)) {
return Groups.find({
sort: {
name: 1
}
});
}
});
The function I'm using really works (so it's not that it's always returning true).
In the client-code I wrote the following:
Meteor.subscribe("myGroups");
Groups = new Meteor.Collection("groups");
Now when I do Groups.find{}); at the client I still get all results (and I should get no result).
Am I misunderstanding something or doing something wrong? I could of course make the collection completely server-side and use Meteor.methods() and Meteor.call() to get the collection data (so that it's always encapsulated by the server). But I really thought it would be cool that I didn't have to do that.
Also I wonder why this can't be done on the same level as insert/update/remove with Collection.allow(). I mean, it would be could that we could have the possibility to add a filter to the map for reading data through find/findOne.
Like #Tarang said, removing autopublish by executing the following command works:
meteor remove autopublish

Resources