How do I insert attributes for users/accounts with MeteorJS? - meteor

I would like to insert attributes to users. If it were another collection I would add a method to a collection. How can I accomplish this with users since it is already built in with Meteor?

You'd add a method just like you would for any other collection - just use Meteor.users as the instance. For example:
server
Meteor.methods({
'users.cats.add: function() {
Meteor.users.update(this.userId, {$inc: {cats: 1}});
}
});
client
Meteor.call('users.cats.add');
You'll find this example particularly useful of your users happen to have cats.

You only need to call:
Meteor.users.update({ _id: myUserId }, { $set: {
someAttribute: 'someValue'
}});
Note that this code will only work server-side. If you want to add those attributes as soon as each new account is created then Accounts.onCreateUser would be a good place to do that. Look here:
http://docs.meteor.com/#/full/accounts_oncreateuser
for more details.

Related

Meteor 1.3: How to create a client only collection containing some fields from the users db collection?

I want to publish all the users to the client (which will eventually be only the 'profile' field of all the users):
The publication on the server looks like this:
Meteor.publish('users', function users() {
return Meteor.users.find();
});
In my template I then have a subscription that looks like this:
Template.Users_show_page.onCreated(function usersShowPageCreated() {
this.subscribe('users');
});
However the 'users' variable is not available and I still have to access the users via Meteor.users, such as in the following code:
Template.Users_show_page.helpers({
users() {
return Meteor.users.find();
}
});
Why is this?
I think I need to create a client-side collection with my choice of name - i.e. 'users', and then I can access that collection.
Where do I do this and how do I make that sync with the users in the database?
MyCollection = new Meteor.Collection('foo');
Meteor.publish('myPublication', function() {
return MyCollection.find();
});
The code above doesn't create a myPublication variable anywhere. It's just the name of the publication/subscription. You can even have multiple different subscriptions over the same collection. This code returns a cursor for the foo Mongo collection, which you access via MyCollection object.
So, your code doesn't need to create a new Mongo.Collection. Just use Meteor.users because that's the Mongo.Collection object that's already linked to "users" in MongoDB.
If you really want to access the documents in a users variable, you still need to create the helper as you suggested, although it's better you just use Meteor.users instead of the helper below:
Template.template_name.helpers({
users: function(){ return Meteor.users.find() }
});

Meteor observeChanges vs manual update

I have a simple todo schema: (just a sample to draw my question)
{
title: {
type: string
},
value: {
type: string
},
author: {
type: object
},
"author._id": {
type: string
},
"author.firstName": {
type: string
},
"author.lastName": {
type: string
},
}
The author entries are from meteor.user. If the meteor user changes the firstName or lastName i have to update the todo. I have two possibilities:
observerChanges (server side) to users collection and update all todos from this user with the new firstname/lastname
if i call the user update method i can call a method to update all todos
when it's better to use cursor.observeChanges and when it's better to call a update method manual? And why?
As the comment says, you should not store the author name / email in the document if it is mutable:
Store the ID of the user only in the document, the UserID is immutable.
When building your ToDo template, look up the User information by ID: you would need to publish a Publication for user by Id, and subscribe to it on the client with the userId as parameter.
Meteor.publish('userById', function(userId) {
return Meteor.users.find({_id: userId}, {limit:1});
});
in your route / template.onCreated depending on your Router, assuming the document is called doc
this.subscribe('userById', this.doc.author._id);
in the template helper
Template.todoTemplate.helpers({
'Author': function() {
return Meteor.users.findOne({_id: this.doc.author._id});
}
});
and call the Author info in the template
<Template name="todoTemplate">
First Name: {{Author.first_name}}
Last Name: {{Author.last_name}}
</Template>
I think you shouldn't rely on the second method, because sometimes you (or your teammate) might forget to update it. Moreover, if you're denormalizing user data in other collections, users knowing Meteor might just call your Meteor.method or manipulate db from the browser console...
You can use this package:
meteor add matb33:collection-hooks
It adds some hooks to your mongo insert/update/remove call
For example:
Meteor.users.after.update(function (userId, doc, fieldNames, modifier, options) {
if (this.previous.firstName === doc.firstName && this.previous.lastName === doc.lastName) {
return;
}
Todos.update({'author._id': doc._id}, {
$set: {
'author.firstName': doc.firstName,
'author.lastName': doc.lastName,
}
})
}, {fetchPrevious: true})
(To update the Todos collection efficiently, make sure to add index to author field)
This is just a handier way than writing your own observeChanges, and better than manually updating Todos collection every time you update the users collection, because you might forgot to call it in some case, or some hacker user just calls Meteor.users.update(Meteor.userId(), {...}) perhaps...
But still, I think you should always add some auto-correct mechanism to avoid wrong data being displayed, because no matter which method you choose, some error will occur (maybe the server watching the db just crashes right after users update). You can check on the client side when displaying content, if author.firstName doesn't match Meteor.users.findOne(author._id) (but you have to publish the user though...), than call a method to tell the server to update it.

Finding documents in meteor.js

I'd like if someon could clarify this point about subscriptions in Meteor.js:
as written in the docs the find() function returns a cursor, not the data, that needs to be fetched;
let's say I have a publish function:
Meteor.publish('pages', function() {
return Pages.find()
});
now let's say I need only the page with code: "one"; in a template helper I can fetch that document:
Template.mytemplate.helpers({
data: function() {
return Pages.find({code: "one"});
}
});
The question is: is correct to do this, or if I need only one document would be better to subscribe only that document? (something like:
Meteor.publish('page', function(code) {
return Pages.find({code: code})
});
)
I mean: does it changes something in terms of efficiency/performance?
Yes, it does change a lot. If you subscribe to the entire collection, then the entire collection will be send to the client and kept in sync with the server. So by all means, if you only need one document, and do not need to cache any of the other documents (e.g., for switching to a new document), then only subscribe to the one you need.

Meteor: How to assign different roles to users during sign up process

I am using the meteor package ian:accounts-ui-bootstrap-3 for accounts and alanning:roles for assigning roles.
On the sign up form I have two options one for Doctor and one for Advisor. I want to assign the selected option as a role to that user. Can someone let me know how to do this?
I have just started learning meteor and don't know much about its flow. I can assign roles to a user if I create the user manually like this:
var adminUser = Meteor.users.findOne({roles:{$in:["admin"]}});
if(!adminUser){
adminUser = Accounts.createUser({
email: "mohsin.rafi#mail.com",
password: "admin",
profile: { name: "admin" }
});
Roles.addUsersToRoles(adminUser, [ROLES.Admin]);
}
But I want to assign a roll automatically as a user signs up and select one of the options and that option should be assigned as his role.
You shouldn't need a hack for this. Instead of using Accounts.onCreateUser you can do it with the following hook on the Meteor.users collection. Something along the lines of the following should work:
Meteor.users.after.insert(function (userId, doc) {
if (doc.profile.type === "doctor") {
Roles.addUsersToRoles(doc._id, [ROLES.Doctor])
} else if (doc.profile.type === "advisor") {
Roles.addUsersToRoles(doc._id, [ROLES.Advisor])
}
});
To get around having to check on login every time it's possible to directly set the roles on the user object instead of using the Roles API.
A hack? Yep, you probably need to make sure the roles have already been added to roles... not sure if there's anything else yet.
if(Meteor.isServer){
Accounts.onCreateUser(function(options, user){
if(options.roles){
_.set(user, 'roles.__global_roles__', ['coach', options.roles]);
}
return user;
});
}
Note: _.set is a lodash method not in underscorejs.
There's no pretty solution because:
There's no server side meteor callback post complete account creation.
In onCreateUser the user hasn't been added to the collection.
Accounts.createUser's callback is currently only for use on the client. A method could then be used from that callback but it would be insecure to rely on it.
The roles package seems to grab the user from the collection and in onCreateUser it's not there yet.
you can use the Accounts.onCreateUser hook to manage that.
Please keep in mind the code below is fairly insecure and you would probably want to do more checking beforehand, otherwise anyone can assign themselves admin. (from docs):
options may come from an untrusted client so make sure to validate any values you read from it.
Accounts.onCreateUser(function (options, user) {
user.profile = options.profile || {};
if (_.has(options, 'role')) {
Roles.addUserToRoles(user._id, options.role);
}
return user;
});
Thanks for your response. I tried but it doesn't work for me.
I used Accounts.onLogin hook to to manage this. Below code works for me:
Accounts.onLogin(function (info) {
var user = info.user;
if(user.profile.type === "doctor"){
Roles.addUsersToRoles(user, [ROLES.Doctor])
}
else
if(user.profile.type === "advisor"){
Roles.addUsersToRoles(user, [ROLES.Advisor])
}
return user;
});

How to honour user privacy settings in Meteor

I have a set of users defined like this:
Accounts.createUser({
username:'Simon',
email:'simon#email.com',
profile:{
firstname:'Simon',
lastname:'Surname',
location:'Home Address',
privacy: {
location:0,
emails:0 } //Location and emails are private and should not be disclosed
}
});
My question is how can I publish this user's record for other users to view, taking into account the profile privacy settings. In this example, I have set the privacy for location and emails to zero with the intention that this information is not published for this user.
I would like to publish it using the standard method:
Meteor.publish("usersWithPublicEmails", function () {
return Meteor.users.find();
});
But I cannot see a way to specify the selector or fields in such a way that only public information will be published.
I have tried adding additional publications of the form:
Meteor.publish("allUsers", function () {
return Meteor.users.find( {}, {fields:{username:1}} );
});
Meteor.publish("usersWithPublicEmails", function () {
return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{username:1, emails:1}} );
});
but the selector does not seem to be returning the emails as I expected. I am looking for optimal way to do this from a performance point of view.
Mongodb is not a relational database so whenever I want to join or query based on metadata I remember I have to do things differently. In your case I would make a separate Collection for user privacy if I wanted to query on user privacy. In addition, if I cared about performance I probably would never want "all of x", I would just want enough to show the user, thus paginate. With these two ideas in mind you can easily get what you want: query based on privacy settings and performance.
Privacy = new Mongo.Collection("privacy");
Whenever we want to add privacy to an account:
Privacy.insert({
emails: 1,
userId: account._id,
});
Then later, one page at a time, showing ten results each page, tracking with currentPage:
Meteor.publish("usersWithPublicEmails function (currentPage) {
var results = []
var privacyResults = Privacy.find({"emails":1}, {skip: currentPage,
limit: 10});
var result;
while (privacyResults.hasNext() ) {
result = privacyResult.next();
results.append(Meteor.users.find({_id: result.userId});
}
return result;
});
I didn't test this code, it may have errors, but it should give you the general idea. The drawback here is that you have to keep privacy and users in sync, but these are the kinds of problems you run into when you're not using a relational database.
Mongodb has a way to do this kind of reference lookup with less code, but it still happens on demand and I prefer the flexibility of doing it myself. If you're interested take a look at Database references
That's because you have a typo in your publish function's fields object, instead of email you've typed emails
So the correct function would be:
Meteor.publish("usersWithPublicEmails", function () {
return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{username:1, email:1}} );
});
Furthermore, you're already publishing all usernames in your allUsers publication, therefore, in order to add the missing data for relevant public users, you'll just need this:
Meteor.publish("usersWithPublicEmails", function () {
return Meteor.users.find( {"profile.privacy.emails":1}, {fields:{email:1}} );
});
and Meteor will automatically merge those records for you.
A simple solution in the end. I had missed the additional subscription in my router:
Router.route('/users', {
name: 'userList',
waitOn: function(){
return Meteor.subscribe('allUsers') &&
Meteor.subscribe('usersWithPublicEmails');
},
data: function(){
return Meteor.users.find();
}
});
A basic mistake:-(

Resources