I have a posts model that I have published and is working fine. However, I have added the following field via simpleSchemas plugin:
userEmail: {
type: String,
autoValue: function() {
if (this.isInsert) {
return Meteor.user().email;
} else if (this.isUpsert) {
return {$setOnInsert: Meteor.user().email};
} else {
this.unset();
}
}
}
When I have this enabled, submit forms don't work, but don't raise any errors. Am I perhaps calling on Meteor.user().email incorrectly? How do I associate the userEmail field with the email of the user who created the post?
The correct syntaxis is.
Meteor.user().emails[0].address
The default Meteor.users collection stores emails in an array (To support multiple emails). Something like return Meteor.user().emails[0].address should work.
Related
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');
I have a mobile app in development and I'm transforming one of the collections to get the user last seen time, avatar etc.
PlayerRecord.prototype = {
constructor : PlayerRecord,
getAssociatedUser: function () {
return Meteor.users.findOne( this.user_id );
},
lastSeenFormatted: function () {
var user = this.getAssociatedUser();
return (user && user.last_seen) ? user.last_seen : 'Never';
}
}
My problem is that, if the user last seen returns Never initially but then the user is seen, my string return over there is not updated...obviously.
How would you advise me to handle this situation?
Did you check whether any user had a value for last_seen? This field has to be explicitly published.
According to the Meteor docs (http://docs.meteor.com/#/full/meteor_user):
By default, the current user's username, emails and profile are
published to the client. You can publish additional fields for the
current user with:
// server
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'last_seen': 1}});
} else {
this.ready();
}
});
// client
Meteor.subscribe("userData");
When using a before.insert collection hook like this:
collection.before.insert(function(userId, doc) {
doc.createdAt = Date.now();
});
if createdAt is a mandatory field (declared with SimpleSchema), then as validation occurs before the insert hook, the server is firing a "Error: createdAt is required" exception.
How validate the schema after the insert hook has occured ?
Add Collection2 to your project (meteor add aldeed:collection2 assuming it isn't already added), and make use of autoValue. AutoValue operations are run on the client only for validation, but the actual value saved will always be generated on the server, regardless of whether the insert/update is initiated from the client or from the server. From the docs.
createdAt: {
type: Date,
autoValue: function() {
if (this.isInsert) {
return new Date;
} else if (this.isUpsert) {
return {$setOnInsert: new Date};
} else {
this.unset();
}
}
}
Your other option would be to set the field as optional, and use your hook, but that's not ideal.
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).
In my meteor.js app, I'm trying to write a simple admin page which can find a user by his/her email address.
I can see that in the Meteor.users collection there is an 'emails' array, which has objects like so
{ address : 'foo#foo.com',
verified : false
}
Normally in Mongodb I can search inside this 'emails' array like so :
Meteor.users.find({ emails.address : 'foo#foo.com' });
But this query is throwing an error :
While building the application:
client/admin.js:224:41: Unexpected token .
Aka Meteor doesn't like the nested query...
Any ideas on how to query the Meteor.users collection by email address ?
You can also use what you had, just put it in quotes:
Meteor.users.find({ "emails.address" : 'foo#foo.com' });
If on the server, Meteor has a special function for this :
Accounts.findUserByEmail(email).
I believe this is the recommended way.
Emails holds an array of emails. Each email has an address.
Try { emails: { $elemMatch: { address: "foo#foo.com" } } }.
Information on $elemMatch is here.
Information on emails as an array is here.
By default, Meteor only publishes the logged in user and you can, as you mention, run queries against that user. In order to access the other users you have to publish them on the server:
Meteor.publish("allUsers", function () {
return Meteor.users.find({});
});
And subscribe to them on the client:
Meteor.subscribe('allUsers');
And run the following command
Meteor.users.find({"emails": "me#example.com"}).fetch()
OR
Meteor.users.find({"emails.0": "me#example.com"}).fetch()
Refer this
If you want to find all emails inside Accounts array, and do an insensitive query:
const hasUser = Meteor.users.findOne({
emails: {
$elemMatch: {
address: {
$regex : new RegExp(doc.email, "i")
}
}
}
});
One possible workaround, if this works on the server but not the client, is to use a users_by_email method on the server:
if (Meteor.isServer) {
Meteor.methods({
'get_users_by_email': function(email) {
return Users.find({ emails.address: email }).fetch();
}
});
}
if (Meteor.isClient) {
foo_users = Meteor.call('get_users_by_email', 'foo#bar.baz');
}