Meteor: Subscription doesn't work - meteor

I'm trying to get a document from the server and display it on the client but the subscription always return a collection with no document.
// server/publications.js
Meteor.publish('myPages', function() {
return Pages.findOne({userId: this.userId});
});
// collection/pages.js
MyPages = new Meteor.Collection('myPages');
// client/main.js
Meteor.subscribe('myPages');
// client/view.js
Template.myView.helpers({
myPages: function(e, t) {
console.debug(MyPages.find({}));
return MyPages.find({});
}
});

You cannot move a document between collections via a subscription. If you subscribe to get a document that's in Pages collection, defined as new Meteor.Collection("pages"), then no matter how your pubsub channels look like, on the client the document will be found in the collection defined as new Meteor.Collection("pages"). So remove all traces of MyPages and use Pages on the client as well. You'll find the document there.

I don't think you can use findOne to publish collections: it doesn't return a cursor but an actual object.
Does this not work?
Meteor.publish('myPages', function() {
return Pages.find({userId: this.userId});
});
or, if necessary:
Meteor.publish('myPages', function() {
return Pages.find({userId: this.userId}, {limit: 1});
});

Related

Why is this collection empty? (Collection.find() is successful om server)

Ok this is what I've got.
The collection called Posts has content and I want to publish this under the name Merchs, the find() in the publish-function finds data but that is not shared to the client where Merchs is always empty.
//shared
Merchs = new Meteor.Collection('merchs');
// Posts has data I want to publish as "Merchs"
this.Posts = new Meteor.Collection('posts');
//server
Merchs.allow({
insert: function(userId, doc) {
return true;
},
update: function(userId, doc, fields, modifier) {
return true;
},
remove: function(userId, doc) {
return true;
}
});
Meteor.publish('merchs', function(data) {
return Posts.find();
});
//client
Deps.autorun( function() {
Session.get('selectedCategories');
subs.subscribe('merchs');
});
When creating your collection, the name in parentheses should be the name of the Mongo collection.
Merchs = new Meteor.Collection('merchs');
Should be:
Merchs = new Mongo.Collection('Posts');
That is, unless you already have a Posts variable defined in code that you didn't show. If you've already defined Posts and you're just looking to make another subscription to the same collection then you don't need this line at all:
Merchs = new Meteor.Collection('merchs');
You also don't need your allow() method (you can just use the one defined for Posts). All you need is the publish() method that you defined.
On the client side you also need:
Meteor.subscribe('merchs');
Also note the use of Mongo.Collection instead of Meteor.Collection which was renamed in Meteor 0.9.1.
You might want to read this excellent answer regarding publish/subscribe: https://stackoverflow.com/a/21853298/4665459

Updating client view in MeteorJS application

I'm new to MeteorJS and there is something that I'm not understanding with subscriptions and publications. I have a tablets collections. This is my setup with autopublish removed.
Server/publictions.js
Meteor.publish('tablets', function() {
return Tablets.find({}, {sort: {manufacturer: 1}});
});
In Client/view.js
Meteor.subscribe('tablets');
Template.tabletsList.helpers({
tablets: function() {
return Tablets.find();
}
});
Then in client/view.html
{{#each tablets}}
{{> tabletPreview}}
{{/each}}
This all works fine and I can see my tablets. But now I have a search box and when the search is submitted I want to update the tablets view to only show the search results.
I have a events handler but can't figure out how to update the tablets to only show the search results as the below code doesn't work. Should I use a session instead or have a totally new view.
client.view.js
Template.tabletsList.events({
"click .search": function (event, template) {
var query = $('input[name=search]').val();
Template.tabletsList.tablets = Tablets.find({manufacturer: query}, {sort: {manufacturer: 1}});
}
});
Here's the approach:
Add a reactive variable to your template on creation which holds the user's last search.
When the user updates the search, you should update the reactive variable.
Use the reactive variable in your helper to fetch only the matching documents.
Here is one possible implementation:
Template.tabletsList.created = function() {
// add a reactive variable to this template to track the search
this.search = new ReactiveVar('');
};
Template.tabletsList.events({
'click .search': function (event, template) {
var query = $('input[name=search]').val();
// update the search based on the user's input
template.search.set(query);
}
});
Template.tabletsList.helpers({
tablets: function() {
// read the user's last search (if any)
var search = Template.instance().search.get();
// sort options
var options = {sort: {manufacturer: 1}};
if (search.length) {
// find all tablets matching the search expression
var re = new RegExp(search);
return Tablets.find({manufacturer: re}, options);
}
else {
// if the user didn't input a search just find all tablets
return Tablets.find({}, options);
}
}
});
Notes:
See this post for more details on scoped reactivity.
Sorting in a publish doesn't really do anything for you in most cases. See my article on common mistakes.

What is wrong with my subscription method on my route?

Can someone see why the route is not subscribing to the publication. Profiles = new Meteor.Collection('profiles');
The mongo database does have documents in this collection, but the browser console still has a count of 0 in Profiles collection.
I am trying to tell the router, "subscribe to user-profile publication, when you are ready, render the 'profile' template. I also named the route 'profile.'
Now I have noticed that after typingsub = Meteor.subscribe('user-profile'); and then sub.ready(); I get the count of the collection. Otherwise the path is not subscribed. This behaviour has not occurred before.
lib/router.js
Router.plugin('loading', {loadingTemplate: 'Loading'});
Router.route('user/profile', {
name: 'profile',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('user-profile');
},
action: function () {
// this.ready() is true if all items returned from waitOn are ready
if (this.ready())
this.render('profile');
else
this.render('Loading');
}
});
server.js:
Meteor.publish('user-profile', function () {
return Profiles.find({userId: this.userId});
});
userId is a field in the Profiles collection. This profiles doc id is stored within the user.profile.experiences array for reference.
Meteor.userId is a function which returns the _id, not the _id itself, and you can't pass a function over DDP anyway. It should be:
waitOn: function () {
return Meteor.subscribe('user-profile', Meteor.userId());
}

Adding an array object to Users collection in Meteor.js

I'm very new to Meteor.js and I'm finding the documentation a bit hard to understand.
I'm starting with a very simple app where Users will simply be allowed to add existing Games to their profile by clicking a button. The Games are stored in another Meteor Collection.
In rails I would just create a has_and_belongs_to_many relationship but that isn't how Meteor works. I thought the best way would be to add an empty array when the user's account is created - then, when they click the "add game" button it would pass the game's title into the users array.
I have this in my /server/users.js file:
Accounts.onCreateUser(function(options, user){
user.games = [];
return user;
});
Meteor.methods({
addGame: function(title) {
Meteor.users.update(Meteor.userId(), { $addToSet: { games: title}});
}
});
And I'm making a call to the addGame method in my /client/views/games/games_list.js file as such:
Template.gamesList.events({
'click .add-to-chest-btn': function(e){
var title = $(e.target).attr('name');
e.preventDefault();
Meteor.call('addGame', title, function(title){ console.log(title)});
}
});
Am I on the right track or is there a better way to do this?
You're on the right track, but do declare an array instead of an object:
Accounts.onCreateUser(function(options, user){
user.games = [];
return user;
});
Push the value directly instead of an object, and use $addToSet to avoid duplicates in case you push the same gameId multiple times:
Meteor.methods({
addGame: function(gameId) {
Meteor.users.update(Meteor.userId(), { $addToSet: { games: gameId }});
}
});

How to explicitly unsubscribe from a collection?

I have a MongoDB with a large "messages" collection; all messages belonging to a specific groupId. So have started with a publication like this:
Meteor.publish("messages", function(groupId) {
return Messages.find({
groupId: groupId
});
});
and a subscription like this:
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
This got me into trouble because initially currentGroupId is undefined but sill mongod would use up the CPU to find messages with groupId == null (although I know there are none).
Now, I tried to rewrite the publication as follows:
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
} else {
return {}; // is this the way to return an empty publication!?
}
});
and/or to rewrite the subscription to:
Deps.autorun(function() {
if (Session.get("currentGroupId")) {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
} else {
// can I put a Meteor.unsubscribe("messages") here!?
}
});
which both helps initially. But as soon as currentGroupId becomes undefined again (because the user navigates to a different page), mongod is still busy requerying the database for the last subscribed groupId. So how can I unsubscribe from a publication such that the mongod is stopped being queried?
According to the documentation it must be http://docs.meteor.com/#publish_stop
this.stop()
Call inside the publish function. Stops this client's subscription;
the onError callback is not invoked on the client.
So something like
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
} else {
return this.stop();
}
});
And I guess on the client side you can just remove your if/else like in your first example
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
I found it more simple and straight-forward to call the .stop() function on the handler which is returned from the .subscribe() call:
let handler = Meteor.subscribe('items');
...
handler.stop();
Simply adding a condition to the publication:
Meteor.publish("messages", function(groupId) {
if (groupId) {
return Messages.find({
groupId: groupId
});
});
and keeping the subscription:
Deps.autorun(function() {
return Meteor.subscribe("messages", Session.get("currentGroupId"));
});
does the job.
There is no need to stop the publication explicitly. Eventually, the MongoDB is not queried anymore after finishing the currently running query and issuing yet another one (which seems to be queued somewhere in the system).
in your case, you should stop the autorun
there is an example in the documentation
Your autorun is actually called with a parameter that allows you to stop it:
Deps.autorun(function (c) {
if (! Session.equals("shouldAlert", true))
return;
c.stop();
alert("Oh no!");
});

Resources