What is wrong with my subscription method on my route? - meteor

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());
}

Related

Meteor.user with Additional Fields on Client

In Meteor, one can add additional fields to the root-level of the new user document like so:
// See: https://guide.meteor.com/accounts.html#adding-fields-on-registration
Accounts.onCreateUser((options, user) =>
// Add custom field to user document...
user.customField = "custom data";
return user;
});
On the client, one can retrieve some data about the current user like so:
// { _id: "...", emails: [...] }
Meteor.user()
By default, the customField does not exist on the returned user. How can one retrieve that additional field via the Meteor.user() call such that we get { _id: "...", emails: [...], customField: "..." }? At present, the documentation on publishing custom data appears to suggest publishing an additional collection. This is undesired for reasons of overhead in code and traffic. Can one override the default fields for Meteor.user() calls to provide additional fields?
You have a couple of solutions that you can use to solve this.
Null Publication
Meteor.publish(null, function () {
if (this.userId !== null) {
return Meteor.users.find({ _id: this.userId }, { fields: { customField: 1 } });
} else {
return this.ready();
}
}, { is_auto: true });
This will give you the desired result but will also result in an additional database lookup.. While this is don't by _id and is extremely efficient, I still find this to be an unnecessary overhead.
2.Updating the fields the Meteor publishes for the user by default.
Accounts._defaultPublishFields.projection = { customField: 1, ...Accounts._defaultPublishFields.projection };
This has to be ran outside of any Meteor.startup blocks. If ran within one, this will not work. This method will not result in extra calls to your database and is my preferred method of accomplishing this.
You are actually misunderstanding the documentation. It is not suggesting to populate and publish a separate collection, just a separate publication. That's different. You can have multiple publications/subscriptions that all feed the same collection. So all you need to do is:
Server:
Meteor.publish('my-custom-user-data', function() {
return Meteor.users.find(this.userId, {fields: {customField: 1}});
});
Client:
Meteor.subscribe('my-custom-user-data');

Tracker autorun using findone

I have this piece of code in client side:
Tracker.autorun(function () {
if (params && params._id) {
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
}
}
});
params will be passed into the url. So, initially we won't have the department data and the findOne method will return null, and then later on, when data arrives, we can find the department object.
But if user enters an invalid id, we need to return them 404. Using tracker autorun, how can I distinguish between 2 cases:
a. Data is not there yet, so findOne returns null
b. There is no such data, even in server's mongodb, so findOne will also returns null.
For case a, tracker autorun will work fine, but for case b, I need to know to return 404
I would suggest you to subscribe to data inside template, like below so you know when subscriptions are ready, then you can check data exists or not
Template.myTemplate.onCreated(function onCreated() {
const self = this;
const id = FlowRouter.getParam('_id');
self.subscribe('department', id);
});
Template.myTemplate.onRendered(function onRendered() {
const self = this;
// this will run after subscribe completes sending records to client
if (self.subscriptionsReady()) {
const id = FlowRouter.getParam('_id');
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
// found data in db
} else {
// 404 - no department found in db
}
}
});
If you are using Iron-Router, you may try this hack.
Router.route('/stores', function() {
this.render('stores', {});
}, {
waitOn: function() {
return [
Meteor.subscribe('stores_db')
];
}
});
The sample code above will wait for the subscription "stores_db" to complete, before rendering anyhing. Then you can use your findOne logic no problems, ensuring that all documents are availble. This suits your situation.
This is what I used to do before I completely understand MeteorJS publications and subscriptions. I do not recommend my solution, it is very bad to user experience. Users will see the page loading forever while the documents are being download. #Sasikanth gave the correct implementation.

Meteor method create insert hook and bind userId on the server

I implemented a hook function, where I attach some createdAt and updatedAt fields to the doc that is inserted to a collection. I can attach this to any collection like this:
export const insertHook = function (doc) {
try {
const user = Meteor.user();
doc.createdBy = user && user._id ? user._id : null;
doc.createdAt = new Date().getTime();
} catch (e) {
console.err(e);
}
};
Attaching the hook to the collection is basically passing it via a third option in the constructor:
class HookedCollection extends Mongo.Collection {
constructor(name, options, hooks={}) {
super(name, options);
this.insertHook = hooks.insertHook;
}
insert(doc, callback) {
if (this.insertHook && Meteor.isServer)
this.insertHook.call(this, doc);
}
}
export const MyDocs = new HookedCollection("mydocs", {}, {insertHook});
In a Meteor method I just do a normal insert:
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc);
}
});
Which creates basically the following error:
Error: Meteor.userId can only be invoked in method calls or publications.
I tried several ways of bind but always ended up in this error. Is there really no way at all to bind the userId to the function?
According to Meteor docs Meteor.userId() is available anywhere but publish functions (Server side Publish function).
You aren't using Meteor.userId() directly in the method but in a callback (see discussion in this github issue). You can pass the userId information to your callback function as a parameter from the method, for example:
// Using Meteor.userId()
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, Meteor.userId());
}
});
// Or using this.userId
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, this.userId());
}
});
As a general rule use Meteor.userId() in the client (that queries the database) and this.userId in the server. More information in this other question Meteor - Why should I use this.userId over Meteor.userId() whenever possible? and in Meteor forums

Meteor Iron Router Run function when collection changes

Im new to Meteor and Im trying to figure out how to run a function after a collection change.
I have a route(iron router) that subscribes to a collection with waitOn. Which just waits for the subscrition to be ready before rendering which is what I want.
waitOn: function () {
return Meteor.subscribe('collection', this.params._id);
},
Any changes to the collection will be updated on all the clients and rendered automatically.
How would I run a function once the collection has changed?
You can use the onData hook, provided that you're returning that data using the data helper. E.g this is what a route may look like
this.route('routename',
path : '/abc',
waitOn: function () {
return Meteor.subscribe('collection', this.params._id);
},
data: function() {
return Collection.findOne({_id: this.params.id});
}
onData: function() {
//Do something when the data found by the above changes
}
});

Meteor: Subscription doesn't work

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});
});

Resources