How to update the content of Meteor.user().profile attributes? - meteor

I have a button that supposed to decrease an attribute inside the user if it is pressed,
I want to decrease the annualLeave attribute when its pressed.
"profile" : {
"annualLeave" : 14,
"replancementLeave" : 0,
"medicalLeave" : 7
}
}
Here is my code to get the userID:
Session.set('getUserId', Al.findOne({_id: Session.get('appealId')}));
console.log(Session.get('getUserId').userID);
The appealId session has the _Id of the current item. inside the item the _Id of the user, in which the item belongs to is stored in userID.
After I get the userID, I use it to retrieve the annualLeave. Here is the code:
Session.set('userDetails', Meteor.users.findOne({_id: Session.get('getUserId').userID}));
console.log(Session.get('userDetails').profile.annualLeave);
until now, both console.log prints out the values successfully.
Now to final step, I want to decrease the annualLeave by 1 and then update the user information in the database,
here is my code (doesn't work):
Session.set('counter', Session.get('userDetails').profile.annualLeave - 1);
Meteor.users.findOne({_id : Session.get('getUserId').userID},{$set:{profile.annualLeave: Session.get('counter')}});
What am I doing wrong the last part?

You need to put the key in quotes if it's not at root level and you need to use the .update() method instead of .findOne()
Meteor.users.update(Session.get('getUserId').userID,
{ $set: { "profile.annualLeave": Session.get('counter') }});
You can also skip part of your use of Session by just decrementing the value:
Meteor.users.update(Session.get('getUserId').userID,
{ $inc: { "profile.annualLeave": -1 }});
Note that if you're only searching by _id you can just pass the value as the first parameter instead of an object.

Related

Firestore rule to only allow update on value change

I have a function that is evaluating if an update is allowed for a user's profile. As you can see, the validProfileUpdate function calls the validFieldUpdate function for each field I have listed (in this case name and age). When I execute an update command for just one of the fields it will work, but when I uncomment the second one (in this case, for age) it will always fail. I only want these fields to be allowed to update based on if there's a change in data between what's being sent in and what already exists.
function validProfileUpdate() {
let validKeys = ['name', 'age'];
return request.resource.data.diff(resource.data).changedKeys().hasOnly(validKeys) &&
validFieldUpdate('name', validName()) &&
validFieldUpdate('age', validAge());
}
function validFieldUpdate(field, condition) {
return !(field in request.resource.data.keys()) ||
(request.resource.data[field] != resource.data[field] && condition);
}
What I'm having difficulty with is that I figured the line !(field in request.resource.data.keys()) in validFieldUpdate would catch any fields not included in the update that's sent and it would return true but for some reason that's not happening when I add the second field age.
So in summary, this works and only allows updates after it's sent for names that aren't Joe:
const profilePayload = {
name: 'Joe'
}
await userProfileRef.update({ ...profilePayload })
But this is blocked by the rules 100% of the time:
const profilePayload = {
name: 'Joe'
age: 25
}
await userProfileRef.update({ ...profilePayload })
You can use affectedKeys to compare the items rather than changedKeys as changedKeys only accounts for key values that are different from the original, a problem if the value stays the same.
let validKeys = ['name', 'age'];
if request.resource.data.diff(resource.data).affectedKeys().hasOnly(validKeys);
Affected:
which lists any keys that have been added to, removed from or modified from the Map calling diff() compared to the Map passed to diff()
Changed:
which lists any keys that appear in both the Map calling diff() and the Map passed to diff(), but whose values are not equal.

How to update a pushed Fire Base object

Pushed firebase objects are hidden behind a unique key, making referencing a pushed object difficult unless your using childAdded. And a reference is needed if you want to use update on the object.
{
"ideaCount" : 0,
"ideas" : {
"-KNVuaB6ZaRCLmH0q8ic" : {
"idea" : {
"likes" : 0,
"name" : "test"
}
},
//unique key, every idea will have a new one of these
"-KNVucJEgZxz_N0P8bcv" : {
//want to reference this
"idea" : {
"likes" : 0,
"name" : "jkl"
}
}
}
Is it possible to update that "idea" object without explicitly putting that unique key in the reference?
Can I start the reference after the unique key? From my experience you need to start the reference at the root.
To be more specific, I would like to be able to change the "likes" number up and down on button press and update seemed to be the best way to do it.
To simply answer your question, no. You will need the push key to complete the reference to ideas/some-idea/likes. However, I am assuming your are listing these "ideas" so people can like them in which you will always have the push key available. For example:
JavaScript
var theIdeas= [];
var ref = firebase.database().ref("ideas");
ref.once("value").then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
// Push key
var key = childSnapshot.key;
// childData will be the actual contents of the child
var childData = childSnapshot.val();
});
theIdeas.push(childData);
}, function(error) {
});
AngularJS
var ref = firebase.database().ref("ideas");
var ideas = $firebaseArray(ref);
<!-- Push key is stored inside $id -->
<div ng-repeat"i in ideas"> {{i.$id}} </div>
Do whatever you wish when you receive that information. The point is that if you have grabbed an "idea" then you do know what the push key is and should be able to use it has part of your reference.
References:
Firebase | key

userId gets into the document magically

This Meteor code is working fine, but I would like to ask if it is the way Meteor does things or it is a un predictable side effect that may change under some condition later.
The things is that when I do
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
Meteor also inserts the correct userId (using 2 different browsers logged in as 2 different users) which I did not explicitly included in the document.
The above line of code is inside a server side function which is called from Meteor method.
here is the relevant information;
//lib/collection.js
DisplayCol = new Mongo.Collection('displayCol');
//server.js
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
In the docs of Collection hooks > Additional notes > second bulleted paragraph says:
userId is available to find and findOne queries that were invoked within a publish function.
But this is a collection.insert. So should I explicitly include the userId in the document or let the collection hook do its hidden magic? Thanks
No, there is no hidden magic in that code, your before hook is inserting the userId field in the document.
When you do an insert like this,
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
the doc that your are inserting is { action: 'task1', element: 'p', value: value_variable }
Because, you have this hook,
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
it changes the doc before inserting into collection. So the above hook will change your doc to {action: 'task1', element: 'p', value: value_variable, userId: 'actual-user-id' }
This is the expected behaviour.
Regarding your other point in the question,
userId is available to find and findOne queries that were invoked
within a publish function.
Previously userId parameter in the find and findOne returns null, so user needs to pass userId as a parameter as mentioned in this comment. Additional notes mentions that the hack is not required any more. It has nothing to do with inserting userId field into the collection document.
To have a quick test, remove the DisplayCol.before.insert hook above, you will not see userId field in the newly inserted documents.
UPDATE
Just to clarify your doubt further, from the 4th point in the docs that you provided
It is quite normal for userId to sometimes be unavailable to hook
callbacks in some circumstances. For example, if an update is fired
from the server with no user context, the server certainly won't be
able to provide any particular userId.
which means that if the document is inserted or updated on the server, there will be no user associated with the server, in that case, userId will return null.
Also you can check the source code yourself here. Check the CollectionHooks.getUserId method, it uses Meteor.userId() to get the userId.
CollectionHooks.getUserId = function getUserId() {
var userId;
if (Meteor.isClient) {
Tracker.nonreactive(function () {
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
});
}
if (Meteor.isServer) {
try {
// Will throw an error unless within method call.
// Attempt to recover gracefully by catching:
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
} catch (e) {}
if (!userId) {
// Get the userId if we are in a publish function.
userId = publishUserId.get();
}
}
return userId;
};

Meteor filtering data when asserting fields from queries

Why does running this code
var userId = Meteor.userId();
var user = Users.findOne(userId, { fields: { earnings: 1 } });
Return
{ _id: 'Co5bMySeaqySgDP6h', earnings: { period: 0.6, total: 52.5 } }
Instead of returning all the fields on the user, including the earnings (custom field)
Also, is there a way to make user queries automatically return custom specified fields, so I dont have to manually specify it each time I need it?
Much appreciated
The reason that you only get the specified field (plus the id) is given in the docs:
To include only specific fields in the result documents, use 1 as the value. The _id field is still included in the result.
If instead you just call Meteor.users.findOne(userId) it will return all of the available fields. If this is called on the server, that will be the entire document, but if you use it on the client, it will only return the fields that have been published from the server, which by default is just the username and the emails and profile fields. Again, per the docs:
On the client, this will be the subset of the fields in the document that are published from the server (other fields won't be available on the client). By default the server publishes username, emails, and profile (writable by user). See Meteor.users for more on the fields used in user documents.
This means that if you have added a new field to you user docs, you need to explicitly publish it for it to be available on the client (assuming autopublish has been removed). Note that it's fine to do this using the previously discussed fields specifier as the other required details (username, profile) will not be overwritten by another publish function unless you try to publish the same top-level field again.
Meteor.publish('earnings', function() {
return Meteor.users.find(this.userId, { fields: { earnings: 1 } });
};
(Publish functions expect you to return a cursor rather than an array, so you need to use find rather than findOne even if there will only be one result).
Finally, it's easy to add your own methods to a collection to make finding stuff you want more concise.
Meteor.users.findSimple = function(selector, options) {
options = options || {};
options.fields = options.fields || {};
options.fields.earnings = 1;
\\ same thing for any other fields you want to limit this find to;
return this.find(selector, options);
};

How can i know specific value on a form using `request.form`?

How can i know specific value on a form using request.form?
I am trying it long but no success.
i want to check something like this
if (request.form.contains("text_check")) //But it doesn't work
{
go in;
}
else{
here we go;
}
i want to know specific value from AllKeys, and total count of all keys too.
To check if a key exists in the form data, you can simply compare the value to null:
if (Request.Form["text_check"] != null) {
If the key exists, you always get a string value back, even if the value is empty.
If you want to check if there is a non-empty value, you can use the String.IsNullOrEmpty method:
if (!String.IsNullOrEmpty(Request.Form["text_check"])) {
If you want to check if a certain key exists in the Request.Form collection you can do so like this:
if(Request.Form.AllKeys.Any(k => k == "text_check")) { ... }
and to then get it's value:
if(Request.Form.AllKeys.Any(k => k == "text_check"))
{
var textCheckValue = Request.Form["text_check"];
}
To get the total number of keys then:
var count = Request.Form.AllKeys.Count();
If you are using server side controls, you can use Request.Form.Contains(text_check.UniqueId) to make sure form is having that value during postback.

Resources