Meteor collection not updating subscription on client - javascript

I'm quite new on Meteor and Mongo and even if I don't want it, I need some relations.
I have a Collection called Feeds and another called UserFeeds where I have a feedid and a userid, and I publish the user feeds on the server like this:
Meteor.publish('feeds', function(){
return Feeds.find({_id:{$in:_.pluck(UserFeeds.find({user:this.userId}).fetch(),'feedid')}});
});
I find the user on UserFeeds, fetch it (returns an array) and pluck it to have only the feedid field, and then find those feeds on the Feeds collection.
And subscribe on the client like this:
Deps.autorun(function(){
Meteor.subscribe("feeds");
});
The problem is that when I add a new feed and a new userfeed the client doesn't receive the change, but when I refresh the page the new feed does appear.
Any idea of what I'm missing here?
Thanks.

I've run into this, too. It turns out publish functions on the server don't re-run reactively: if they return a Collection cursor, as you're doing (and as most publish functions do), then the publish function will run once and Meteor will store the cursor and send down updates only when the contents of the cursor change. The important thing here is that Meteor will not re-run the publish function, nor, therefore, the Collection.find(query), when query changes. If you want the publish function to re-run, then the way I've done it so far is to set up the publish function to receive an argument. That way the client, whose collections do update reactively, can re-subscribe reactively. The code would look something like:
// client
Meteor.subscribe('user_feeds');
Deps.autorun(function(){
var allFeeds = UserFeeds.find({user: Meteor.userId()}).fetch();
var feedIds = _.pluck(allFeeds,'feedid');
Meteor.subscribe('feeds',feedids);
});
// server
Meteor.publish('feeds',function(feedids) {
return Feeds.find({_id: {$in: feedids}});
});
I believe the Meteorite package publish-with-relations is designed to solve this problem, although I haven't used it.
EDIT: I believe the publish function will re-run when the userId changes, which means that you can have a server-side check to make sure the user is logged in before publishing sensitive data.

I think your problem is that .fetch() which you use here…
UserFeeds.find({user:this.userId}).fetch()
…removes the reactivity.
.fetch() returns an array instead of a cursor, and that array won't be reactive.
http://docs.meteor.com/#fetch

try this ...
Meteor.autosubscribe(function(){
Meteor.subscribe("feeds");
});
and in the Template JS ...
Template.templateName.feeds = function()
return Feeds.find() # or any specific call
};
in the HTML ...
{{#each feeds}}
do some stuff
{{else}}
no feed
{{/each}}

You can use the reactive-publish package (I am one of authors). It allows you to create publish endpoints which depend on the result of another query. In your case, query on UserFeeds.
Meteor.publish('feeds', function () {
this.autorun(function (computation) {
var feeds = _.pluck(UserFeeds.find({user: this.userId}, {fields: {feedid: 1}}).fetch(), 'feedid');
return Feeds.find({_id: {$in: feeds}});
});
});
The important part is that you limit the UserFeeds fields only to feedid to make sure autorun does not rerun when some other field changes in UserFeeds, a field you do not care about.

Related

Mongo DB - Why my Users.findOne is Undefined?

I'm working on Meteor, trying to find some values from Mongodb collection.
here is the code:
var sameLogins = Users.findOne({login: 'a'});
console.log(sameLogins);
But it's returning and "undefined".
But record exists in collection:
So, can anybody tell what I'm missing?
Also, in mongo console - everything is working fine:
I was looking in Publish/Subsribe stuff, but i'm using autopublish module yet.
Thank you!
I will leave the answer for this issue for new users having the same problem.
If you're using autopublish package then you should be aware that it's publishing the result of .find() for every collection.
But, Meteor.users.find(), be default, will return only _id and profile fields, so documents in your Meteor.users client collection will have these two fields only.
The most easy workaround for this would be to create your own publication (allUsers, for example) and in it to return those fields you need:
Server:
Meteor.publish('allUsers', () => {
// check for Meteor.userId() is omitted, put it here, if needed
return Meteor.users.find({}, { fields: { ... } });
});
Don't forget to subscribe to it:
Client:
Meteor.subscribe('allUsers');
Update for Meteor:
Right now you are storing a cursor in your variable sameLogins. In order to retrieve the results you want, you must actually execute this query by either calling fetch(). What is returned from findOne without fetch is essentially an object that you could use to iterate over and find mongoDB documents - (called a collection cursor). The cursor is not your result itself.
Calling fetch would like something like:
Users.findOne({login: 'a'}).fetch()

Meteor Collections Publish and Subscriptions

Been a bit stuck today and found a bunch of related topics, but still didn't manage to fix it. I'm kinda new to Meteor and might not been doing it in the right way but got autopublish removed.
I'm creating the collection as a const in lib/import folder which is shared with client/server. Next, I'm calling a server method inside a Async to insert the data into the collection. So far so good, I think (I see the data inside db mongo).
Now in the client.js, I want to handle each data related to a user, and then append it to the template or do some other stuff.
// SERVER
Meteor.publish("pipeline", function() {
var data = pipeline.find({}, {fields: {userID:this.userID}}).fetch();
return data;
});
// CLIENT
var loadCurrentPipeLineUser = Meteor.subscribe('pipeline');
var data = pipeline.findOne({userID: Meteor.userId()});
console.log(loadCurrentPipeLineUser);
console.log(data);
Both loadCurrentPipeLineUser and data return undefined. The output of loadCurrentPipe (which I think means undefined) is:
On server side, inside the publish, it prints everything right on the console.
Publish it without .fetch()
Meteor.publish("pipeline", function() {
return pipeline.find({}, {fields: {userID:this.userID}});
});
Because publish returns a cursor.

How can I only publish a collection when needed?

In my application I have a public profile, that is just a subset of all the fields available for my Meteor.users. I know I can just publish certain fields from the collection, but I feel like when I subscribe to that data, that's a lot of unnecessary data being transferred to the user. So, I want to be able to just publish this data when the user requests it, i.e. when you visit /public-profile/425f7834985r79na8syf
Define a route that takes a parameter (in your case /public-profile/:_id)
Have your route subscribe to a publication using that _id as an argument to the subscription, ex:
Meteor.subscribe('oneProfile',id);
Setup your publication as:
Meteor.publish('oneProfile',function(id){
return Meteor.users.find({_id: id},{fields: {thisField: 1, thatField: 1}});
});
Note that this publish function is returning a cursor even though we only expect a single document. Publish functions cannot return a single document using findOne().

meteorjs subscribe usage when collection is huge

I don't know the best way to handle huge mongo databases with meteorjs.
In my example I have a database collection with addresses in it with the geo location. (the whole code snippets are just examples)
Example:
{
address : 'Some Street',
geoData : [lat, long]
}
Now I have a form where the user can enter an address to get the geo-data. Very simple. But the problem is, that the collection with the geo data has millions of documents in it.
In Meteor you have to publish a collection on Server side and to subscribe on Client and Server side. So my code is like this:
// Client / Server
Geodata = new Meteor.collection('geodata');
// Server side
Meteor.publish('geodata', function(){
return Geodata.find();
});
// Client / Server
Meteor.subscribe('geodata');
Now a person has filled the form - after this I get the data. After this I search for the right document to return. My method is this:
// Server / Client
Meteor.methods({
getGeoData : function (address) {
return Geodata.find({address : address});
}
});
The result is the right one. And this is still working. But my question is now:
Which is the best way to handle this example with a huge database like in my example ? The problem is that Meteor saves the whole collection in the users cache when I subscribed it. Is there a way to subscribe to just the results I need and when the user reused the form then I can overwrite the subscribe? Or is there another good way to save the performance with huge databases and the way I use it in my example?
Any ideas?
Yes, you can do something like this:
// client
Deps.autorun(function () {
// will re subscribe every the 'center' session changes
Meteor.subscribe("locations", Session.get('center'));
});
// server
Meteor.publish('locations', function (centerPoint) {
// sanitize the input
check(centerPoint, { lat: Number, lng: Number });
// return a limited number of documents, relevant to our app
return Locations.find({ $near: centerPoint, $maxDistance: 500 }, { limit: 50 });
});
Your clients would ask only for some subset of the data at the time. i.e. you don't need the entire collection most of the time, usually you need some specific subset. And you can ask server to keep you up to date only to that particular subset. Bare in mind that more different "publish requests" your clients make, more work there is for your server to do, but that's how it is usually done (here is the simplified version).
Notice how we subscribe in a Deps.autorun block which will resubscribe depending on the center Session variable (which is reactive). So your client can just check out a different subset of data by changing this variable.
When it doesn't make sense to ship your entire collection to the client, you can use methods to retrieve data from the server.
In your case, you can call the getGeoData function when the form is filled out and then display the results after the method returns. Try taking the following steps:
Clearly divide your client and server code into their respective client and server directories if you haven't already.
Remove the geodata subscription on the server (only clients can activate subscriptions).
Remove the geodata publication on the server (assuming this isn't needed anymore).
Define the getGeoData method only on the server. It should return an object, not a cursor so use findOne instead of find.
In your form's submit event, do something like:
Meteor.call('getGeoData', address, function(err, geoData){Session.set('geoDataResult', geoData)});
You can then display the geoDataResult data in your template.

Is it a code smell if I have the need to save a Backbone.Collection?

I've been trying to wrap my head around best RESTful practices while using BackboneJS. I feel like I've written myself into a bit of a knot and could use some guidance.
My scenario is this: a user wants to create a new Playlist with N items in it. The data for the N items is coming from a third-party API in bursts of 50 items. As such, I want to add a new, empty Playlist and, as the bursts of 50 come in, save the items and add to my Playlist.
This results in my Playlist model having a method, addItems, which looks like:
addItems: function (videos, callback) {
var itemsToSave = new PlaylistItems();
var self = this;
// Create a new PlaylistItem with each Video.
videos.each(function (video) {
var playlistItem = new PlaylistItem({
playlistId: self.get('id'),
video: video
});
itemsToSave.push(playlistItem);
});
itemsToSave.save({}, {
success: function () {
// OOF TERRIBLE.
self.fetch({
success: function () {
// TODO: For some reason when I call self.trigger then allPlaylists triggers fine, but if I go through fetch it doesnt trigger?
self.trigger('reset', self);
if (callback) {
callback();
}
}
});
},
error: function (error) {
console.error("There was an issue saving" + self.get('title'), error);
}
});
}
ItemsToSave is generally a Collection with 50 items in it. Since BackboneJS does not provide a Save for Collections, I wrote my own. I didn't care much for creating a Model wrapper for my Collection.
So, when I call Save, none of my items have IDs. The database assigns the IDs, but that information isn't implicitly updated by Backbone because I'm saving a Collection and not a Model. As such, once the save is successful, I call fetch on my Playlist to retrieve the updated information. This is terrible because a Playlist could have thousands of items in it -- I don't want to be fetching thousands of items every time I save multiple.
So, I'm thinking maybe I need to override the Collection's parse method and manually map the server's response back to the Collection.
This all seems... overkill/wrong. Am I doing something architecturally incorrect? How does a RESTful architecture handle such a scenario?
My opinion is do what works and feels clean enough and disregard what the RESTafarians credence might be. Bulk create, bulk update, bulk delete are real world use cases that the REST folk just close their eyes and pretend don't exist. Something along these lines sounds like a reasonable first attempt to me:
create a bulkAdd method or override add carefully if you are feeling confident
don't make models or add them to the collection yet though
do your bulk POST or whatever to get them into the database and get the assigned IDs back
then add them as models to the collection

Categories

Resources