Meteor Client-side Collection Document Appears and Disappears - meteor

After lots of reading, I'm starting to get a better handle on Meteor's publish/subscribe model. I've removed the autopublish training wheels from my first app and while I have most everything working, I am seeing one issue.
When the app first loads, my publish and subscribe hooks work great. I have a block of code that runs in a Tracker.autorun() block which makes the subscribe calls, I am able to sequentially wait for data from the server using ready() on my subscribe handles, etc.
One feature of my app is that it allows the user to insert new documents into a collection. More specifically, when the user performs a certain action, this triggers an insert. At that point, the client-side JS runs and the insert into MiniMongo completes. The reactive autorun block runs and the client can see the inserted documented. The client updates the DOM with the new inserted data and all is well.
Furthermore, when I peek into the server-side MongoDB, I see the inserted document which means the server-side JS is running fine as well.
Here's where it gets weird. The client-side autorun block runs a second time (I'm not sure why) and this time, the client no longer has the inserted item. When the DOM renders, the newly inserted item is now gone. If I reload the page, all is well again.
Has anyone seen this behavior before? I'm also noticing that the server-side publish call runs once on page load but then it doesn't run again after the insert. This seems wrong because how else will the client get the reconciled data from the server after the insertion (i.e. after Meteor's client-side latency compensation)?
The important functions (ComponentInstances is the collection that is bugging out):
Publish block:
Meteor.publish('allComponentInstances', function (documentId, screenIndex) {
console.log(`documentId: ${documentId} screenIndex: ${screenIndex}`)
const screens = Screens.find({ownerDocumentId: documentId})
const selectedScreen = screens.fetch()[screenIndex]
return ComponentInstances.find({_id: {$in: selectedScreen.allComponentInstanceIds}})
})
Subscription block in autorun:
// ... a bunch of irrelevant code above
const allComponentInstancesHandle = Meteor.subscribe('allComponentInstances', document._id, 0)
if (allComponentInstancesHandle.ready()) {
isReady = true
screens = Screens.find({ownerDocumentId: document._id}).fetch()
const componentInstanceObjects = ComponentInstances.find().fetch()
allComponentInstances = {}
componentInstanceObjects.map((componentInstance) => {
allComponentInstances[componentInstance._id] = componentInstance
})
}

This is most probably you're inserting documents from client side. And you have not set up your permission rules properly. When you remove autopublish and insecure from your app, you are not allowed to insert/update/remove documents into collection unless you have allow/deny rules set up in the server side.
Meteor has a great feature called latency compensation which tries emulate your db operations before it gets the actual write operation in the db. And when the server tries to write in the db, it looks for allow/deny rules.If the permission rules doesn't allow the db operation or Whatever the reason( either allow/deny or authentication) for not actually written in the db, then the server data gets synchronized with your client side db.
This is why i assume you are seeing your document being inserted for the first time and gets disappeared within a second.
check this section of meteor docs.
http://docs.meteor.com/#/full/allow

I ended up solving this a different way. The core issue, I believe, has nothing to do with accept/deny rules. In fact, their role is still hazy to me.
I realize now what I've been reading all along in the Meteor docs: the publish functions return cursors. If the cursor itself doesn't change (e.g. if you're passing specific keys you want to fetch), then it won't really work as a reactive data source in the sense that new documents in a collection will not make the data publish again. You are, after all, still requesting the same keys.
The way forward is to come up with a publish cursor that accurately reflects the reactive data you want to retrieve. This sounds abstract but in practice, it means make sure the cursor is general, not specific to the specific keys you are retrieving.

Related

Meteor: show maintenance page in client side from a server side variable

I want to use reactive information on the client side to disable some functionalities while the server is performing an important heavy task.
I tried using a publication but even though the subscription in the client was inside an autorun it was not updating the field. I’m not sure if using a publication is the best option.
Let’s say I have a server variable called “IN_MAINTENANCE” and in the client side I want to load a specific template for the maintenance page, but never allowing the user to change this (The variable should only be defined in the server side).
How can I achieve this without storing anything in the Database?
Pretty sure you have to use the database for it to be a reactive update that applies to all users. You could use a settings variable, but that would require an app restart to take effect. This answer has code you can repurpose for this.
I think your first instinct was right in doing a publication. Reacting to a data change is the only way for the server to influence the client. Make sure that your publish and subscribe is working correctly, and then in your autorun function you can take the user (and all users) to the maintenance page.
This is a Meteor brain teaser right? The "without storing anything in the db" is the interesting bit.
Let's say that you have some publication that all clients are getting, whether logged in or not, and that is expected to return at least one document:
Meteor.publish('default',function(){
let query = ...
let options = ...
return MyCollection.find(query,options);
});
You could signal to the client by just returning nothing:
Meteor.publish('default',function(){
if ( IN_MAINTENANCE ) {
return;
} else {
let query = ...
let options = ...
return MyCollection.find(query,options);
}
});
Then on the client you just look to see that the subscription is ready but empty and switch layouts based on that.
There are 100 solutions to this puzzle, this is just a particularly hacky one.

Meteor - How do I automatically redirect user to page when data changes

I am writing a Meteor app that takes in external data from a machine (think IoT) and displays lots of charts, graphs, etc. So far so good. There are various pages in the application (one per graph type so far). Now as the data is being fed in "real-time", there is a situation (normal) where the data "set" gets totally reset. I.e. all the data is no longer valid. When this happens, I want to redirect the user back to the "Home" page regardless of where they are (well, except the home page).
I am hoping to make this a "global" item, but also don't want too much overhead. I noticed that iron:router (that I am using) has an onData()method but that seems a bit -- high overhead -- since it's just one piece of data that indicates a reset.
Since each page is rather "independent" and an user can stay on a page for a long time (the graphs auto-update as the underlying data changes) , I'm not even sure iron:router is the best approach at all.
This is Meteor 1.0.X BTW.
Just looking for a clean "proper" Meteor way to handle this. I could put a check in the redisplay logic of each page, but would think a more abstracted (read: global) approach would be more long-term friendly (so if we add more pages of graphs it automatically still works) ..
Thanks!
This is a job for cursor.observeChanges http://docs.meteor.com/#/full/observe_changes
Set up a collection that servers as a "reset notification" that broadcasts to all users when a new notification is inserted.
On Client:
criteria = {someCriteria: true};
query = ResetNotificationCollection.find(criteria)
var handle = query.observeChanges({
added: function (id, user) {
Router.go('home');
}
});
Whenever a reset happens:
notification = { time: new Date(), whateverYouWantHere: 'useful info' }
ResetNotificationCollection.insert notification
On insert, all clients observing changes on the collection will respond to an efficient little DDP message.

Meteor - how can I empty out the collection of 10,000 objects I subscribed to after I no longer need it?

I have a template in which a user should be able to click on a button to bring up a modal and in the modal choose a handful of items out of a list of about 10,000 items which are displayed there to search or scroll through.
Since this collection is so big, I don't want to keep it around in memory when I don't absolutely need it.
So I would like to subscribe to this collection only when the modal is being viewed and I would like to ensure that I am unsubscribed if the modal is not being viewed.
Is there a way to explicitly unsubscribe from a collection?
There are a couple of ways you can do this:
Use the subscription handle
subscribe returns a handle you can call stop on. For example:
var handle = Meteor.subscribe('stuff');
handle.stop();
Use an autorun
Because an autorun will automatically start and stop subscriptions when its reactive dependencies change, this will work:
Tracker.autorun(function () {
if (Session.get('showingModal'))
Meteor.subscribe('stuff');
});
Side note - it may make more sense to use a method call for searching such a large data set rather than publishing the entire thing to the client. For example you can set a session variable whenever the user's query changes, then use an autorun to update the result set based on the method's return value.
https://docs.meteor.com/#/full/meteor_subscribe
Quoting the docs :
Meteor.subscribe returns a subscription handle, which is an object
with the following methods:
stop() Cancel the subscription. This will typically result in the
server directing the client to remove the subscription's data from the
client's cache.
So basically what you need to do is storing the subscription handle in a variable and call the stop method when you don't need those published documents anymore.
Note that if you're using iron:router (and you probably should), this is taken care of automatically for you on each route change, which is convenient but has the side effect of provoking a lot of sometimes unnecessary calls to Meteor.publish calls which are non trivial for the server and bandwidth... to address this matter you can use meteorhacks:subs-manager but it's another topic anyway.

Update document in Meteor mini-mongo without updating server collections

In Meteor, I got a collection that the client subscribes to. In some cases, instead of publishing the documents that exists in the collection on the server, I want to send down some bogus data. Now that's fine using the this.added function in the publish.
My problem is that I want to treat the bogus doc as if it were a real document, specifically this gets troublesome when I want to update it. For the real docs I run a RealDocs.update but when doing that on the bogus doc it fails since there is no representation of it on the server (and I'd like to keep it that way).
A collection API that allowed me to pass something like local = true this would be fantastic but I have no idea how difficult that would be to implement and I'm not to fond of modifying the core code.
Right now I'm stuck at either creating a BogusDocs = new Meteor.Collection(null) but that makes populating the Collection more difficult since I have to either hard code fixtures in the client code or use a method to get the data from the server and I have to make sure I call BogusDocs.update instead of RealDocs.update as soon as I'm dealing with bogus data.
Maybe I could actually insert the data on the server and make sure it's removed later, but the data really has nothing to do with the server side collection so I'd rather avoid that.
Any thoughts on how to approach this problem?
After some further investigation (the evented mind site) it turns out that one can modify the local collection without making calls to the server. This is done by running the same methods as you usually would, but on MyCollection._collection instead of just on Collection. MyCollection.update() would thus become MyCollection._collection.update(). So, using a simple wrapper one can pass in the usual arguments to a update call to update the collection as usual (which will try to call the server which in turn will trigger your allow/deny rules) or we can add 'local' as the last argument to only perform the update in the client collection. Something like this should do it.
DocsUpdateWrapper = function() {
var lastIndex = arguments.length -1;
if (arguments[lastIndex] === 'local') {
Docs._collection.update(arguments.slice(0, lastIndex);
} else {
Docs.update(arguments)
}
}
(This could of course be extended to a DocsWrapper that allows for insertion and removals too.)(Didnt try this function yet but it should serve well as an example.)
The biggest benefit of this is imo that we can use the exact same calls to retrieve documents from the local collection, regardless of if they are local or living on the server too. By adding a simple boolean to the doc we can keep track of which documents are only local and which are not (An improved DocsWrapper could check for that bool so we could even omit passing the 'local' argument.) so we know how to update them.
There are some people working on local storage in the browser
https://github.com/awwx/meteor-browser-store
You might be able to adapt some of their ideas to provide "fake" documents.
I would use the transform feature on the collection to make an object that knows what to do with itself (on client). Give it the corruct update method (real/bogus), then call .update rather than a general one.
You can put the code from this.added into the transform process.
You can also set up a local minimongo collection. Insert on callback
#FoundAgents = new Meteor.Collection(null, Agent.transformData )
FoundAgents.remove({})
Meteor.call 'Get_agentsCloseToOffer', me, ping, (err, data) ->
if err
console.log JSON.stringify err,null,2
else
_.each data, (item) ->
FoundAgents.insert item
Maybe this interesting for you as well, I created two examples with native Meteor Local Collections at meteorpad. The first pad shows an example with plain reactive recordset: Sample_Publish_to_Local-Collection. The second will use the collection .observe method to listen to data: Collection.observe().

Is there a way to tell meteor a collection is static (will never change)?

On my meteor project users can post events and they have to choose (via an autocomplete) in which city it will take place. I have a full list of french cities and it will never be updated.
I want to use a collection and publish-subscribes based on the input of the autocomplete because I don't want the client to download the full database (5MB). Is there a way, for performance, to tell meteor that this collection is "static"? Or does it make no difference?
Could anyone suggest a different approach?
When you "want to tell the server that a collection is static", I am aware of two potential optimizations:
Don't observe the database using a live query because the data will never change
Don't store the results of this query in the merge box because it doesn't need to be tracked and compared with other data (saving memory and CPU)
(1) is something you can do rather easily by constructing your own publish cursor. However, if any client is observing the same query, I believe Meteor will (at least in the future) optimize for that so it's still just one live query for any number of clients. As for (2), I am not aware of any straightforward way to do this because it could potentially mess up the data merging over multiple publications and subscriptions.
To avoid using a live query, you can manually add data to the publish function instead of returning a cursor, which causes the .observe() function to be called to hook up data to the subscription. Here's a simple example:
Meteor.publish(function() {
var sub = this;
var args = {}; // what you're find()ing
Foo.find(args).forEach(function(document) {
sub.added("client_collection_name", document._id, document);
});
sub.ready();
});
This will cause the data to be added to client_collection_name on the client side, which could have the same name as the collection referenced by Foo, or something different. Be aware that you can do many other things with publications (also, see the link above.)
UPDATE: To resolve issues from (2), which can be potentially very problematic depending on the size of the collection, it's necessary to bypass Meteor altogether. See https://stackoverflow.com/a/21835534/586086 for one way to do it. Another way is to just return the collection fetch()ed as a method call, although this doesn't have the benefits of compression.
From Meteor doc :
"Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass {reactive: false} as an option to find."
I think this simple option is the best answer
You don't need to publish your whole collection.
1.Show autocomplete options only after user has inputted first 3 letters - this will narrow your search significantly.
2.Provide no more than 5-10 cities as options - this will keep your recordset really small - thus no need to push 5mb of data to each user.
Your publication should look like this:
Meteor.publish('pub-name', function(userInput){
var firstLetters = new RegExp('^' + userInput);
return Cities.find({name:firstLetters},{limit:10,sort:{name:1}});
});

Resources