Meteor.user with Additional Fields on Client - meteor

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

Related

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 subscription doesn't refresh despite WaitOn

I'm using Iron Router. I have a RouterController that looks something like this:
var loggedInUserController = RouteController.extend({
layoutTemplate: "GenericLayout",
waitOn: function () {
return Meteor.subscribe("TheDataINeed");
}
});
And I have a route defined which uses this controller to wait for the 'TheDataINeed':
Router.route("/myapp", {
name: "Landing",
controller: loggedInUserController,
data: function () {
if(this.ready()){
return {content: "page-landing"};
}
}
});
Now, the problem is the data I am subscribed to is conditional: meaning, depending on the user's role, I publish different data, like so:
if (!Roles.userIsInRole(this.userId, 'subscribed') ) {
return [
myData.getElements({}, { fields: { _id: 1, title: 1}, limit: 5 })
];
} else {
return [
myData.getElements({}, { fields: { _id: 1, title: 1} })
];
}
When the user's role is not 'subscribed', I limit the published data to 5 elements.
The problem is publishing is not reactive, so when the user changes his role for the first time to 'subscribed' and I navigate to my route ("/myapp"), the user still sees the limited number of elements instead of all of them.
Is there a way to manually re-trigger the subscription when I am loading this route? If possible, I'd like to do this without adding new packages to my app.
Not sure about that approach but can you try to set session value in route instead of subscription code. Then in a file on client side where your subscriptions are you can wrap Meteor.subscribe("TheDataINeed") in Tracker.autorun and have a session as a subscription parameter. Every time that session value is changed autorun will rerun subscription and it will return you data based on a new value.

Add extra user field

In my Meteor app I use the default accounts package, which gives me the default login and registration functionality. Now I want to add an extra field to user, say nickname, and for the logged in user the possibility to edit this information.
For editing the profile I suppose I should be doing something like this:
Template.profileEdit.events({
'submit form': function(e) {
e.preventDefault();
if(!Meteor.user())
throw new Meteor.Error(401, "You need to login first");
var currentUserId = this._id;
var user = {
"profile.nickname": $(e.target).find('[name=nickname]').val()
};
Meteor.users.update(currentUserId, {
$set: user
}, function(error){
if(error){
alert(error.reason);
} else {
Router.go('myProfile', {_id: currentUserId});
}
});
}
});
But I doesn't store the info if I look in Mongo. Also when showing the profile, {{profile.nickname}} returns empty. What is wrong here?
Edit: added collections\users.js to show permissions:
Meteor.users.allow({
update: function (userId, doc) {
if (userId && doc._id === userId) {
return true;
}
}
});
Meteor.users.deny({
update: function(userId, user, fieldNames) {
return (_.without(fieldNames, 'profile.nickname').length > 0);
}
});
Yeah, I believe that should do the job, although I haven't actually run the code. The idea is certainly right.
The main things to be aware of are:
The necessity to allow the user doc to be edited from the client with an appropriate Meteor.users.allow() block on the server, assuming you're going to remove the "insecure" package (which you need to before doing anything in production).
The fact that "by default the server publishes username, emails, and profile", so you'll need to write a Meteor.publish function on the server and subscribe to it if you want to expose any other fields within the user document to the client once you've removed the "autopublish" package (which again, you really should).

Cannot access other users' email addresses in Meteor app

Before I begin, I have followed the steps here: Meteor Querying other users by email
And I have read the Meteor documentation about publishing users and how to add more fields than the id, username, and profile. My situation exists in spite of all of these things.
I'm trying to access other user's email addresses, beyond just the currently logged in user. I have 2 templates that need this access. The first template works and is able to access it. The second template is unable to.
Here is the setup code I have for publishing the emails field, and subscribing (I've also tried not specifying 'address' [e.g. fields: {emails: 1}] but that has the same result)
if (Meteor.isServer) {
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
Meteor.publish("allUserData", function () {
return Meteor.users.find({}, {fields: {"emails.address": 1}});
});
};
if (Meteor.isClient) {
Meteor.subscribe("allUsers");
Meteor.subscribe("allUserData");
};
Here is the code from the template that works:
Template.createPartner.events({
'click .setup-partner' : function(event, template) {
var partner = Meteor.users.findOne({"emails.address": 'example#mail.com' }); <-- works
}
});
Here is the code from the template that doesn't work:
Template.infoSelect.partnerEmail = function() {
var partnerId = Meteor.user().profile.partnerId; <-- works
var partner = Meteor.users.findOne({_id: partnerId}); <-- works but only _id and profile are returned
return partner.emails[0].address; <-- throws exception because the 'emails' field doesn't exist
};
I've also tried this, but no difference:
var partner = Meteor.users.find({_id: partnerId}, {fields: {"emails.address": 1}});
Why can I not see the user's email address in the second template, but I can in the first?
I think its because you're subscribing to two sets of the same collection. Meteor uses the first subscription and ignores the second. I'm not sure why it works on one occasion though.
If you remove the first subscription and go with the second It should work, basically remove the line:
Meteor.subscribe("allUsers");
One more tip. You could alter your email function to:
Template.infoSelect.partner = function() {
var partnerId = Meteor.user().profile.partnerId; <-- works
var partner = Meteor.users.findOne({_id: partnerId}); <-- works but only _id and profile are returned
return partner;
};
And your handlebar would be : (it just opens up more options for your partner variable so you could reference him/her by name too)
<template name="infoSelect">
{{partner.email.0.address}}
{{partner.profile.name}} <!--If you have configured profiles -->
</template>

Right way of accessing Meteor.users from the client

I have defined some useful fields in the users collection for my convenience. What would be the right way of allowing a client to access the corresponding field? I'm using the autopublish package, but Meteor.user() from the client side only reveals the emails array.
You have to explicitly tell Meteor which fields from users to include when querying users collection.
For example to publish custom "avatar" field on client:
// Client only code
if (Meteor.isClient) {
Meteor.subscribe("currentUserData");
...
}
// Server-only code
if (Meteor.isServer) {
Meteor.publish("currentUserData", function() {
return Meteor.users.find({}, {
fields : {
'avatar' : 1
}
});
});
...
}

Resources