In the following is the section of code :
Profile is a mongoose shcema object and contains multiple key-value pairs and the function of the code is to update Profile using findOneAndUpdate method passing in profileFields to $set which contains the new key-value pairs.
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true }
);
console.log(profile);
return res.json(profile);
}
This code works fine when a value for a key is changed.
eg. Profile.githubusername was abcd and is changed by passing profileFields(in which githubusername is xyz) to $set in the findOneAndUpdate method.
But the code fails when a key-value pair is completely removed.
eg. profileFields object does not contain githubusername key.
In such case the earlier previously stored value of Profile.githubusername persists.
But I need all existing information for profile to be replaced by profileFields such that if a key-value pair is missing from profileFields it is also removed from profile.
Is there a way to achieve this?
TIA
Edit :
I added the following before making the call to findOneAndUpdate(). But this still doesn't work.
let profile = await Profile.findOne({ user: req.user.id });
// for (key in profile)
// console.log(key);
for (key in profile) {
if (Object.keys(profileFields).indexOf(key) === -1) {
console.log('missing key: 'key);
profileFields[key] = '';
}
}
When I console.log(profile) I get:
{
skills: [ 'HTML' ],
_id: 60142f8f9a5bff1f08653478,
user: 60142dc89a5bff1f08653477,
company: 'Self Employed',
bio: 'Useless international',
status: 'Student or Learning',
experience: [],
education: [],
date: 2021-01-29T15:53:51.693Z,
__v: 10,
location: 'Ahemdabad, Gujrat, India',
githubusername: 'k'
}
From what I understand skills, _id, user, company, bio ... githubusername are the only keys in this object.
But on running a for (key in profile) loop I get a lot a other keys as well ,some are :
$__
isNew
errors
$locals
$op
_doc
$init
db
discriminators
schema
collection
$__originalValidate
$__save
$__validate
$__remove
$__deleteOne
$__init
$isMongooseModelPrototype
$__handleSave
save
$__delta
$__version
increment
$__where
remove
delete
How can I loop through only the user defined keys?
You can try:
delete mongooseObject["$init"]
it will delete your key and then u can manipulate other keys
Related
I have a schema that includes an array of child references:
const schemaSet = {
userSchema: new Schema({
name: {
type: String,
required: true
}
}),
groupSchema: new Schema({
name: {
type: String,
required: true
},
members: [ {
type: Schema.Types.ObjectId
ref: 'User'
}]
})
}
This is working fine in terms of being able to create groups and add users to them, but I find I can't remove a user from the group.
The closest I have got so far is this:
async removeUser(group, userId) {
console.log("Before: group has "+group.members.length+" members");
await group.members.pull({ _id : userId });
console.log("After: group has "+group.members.length+" members");
await group.save();
}
This logs out the correct size before and after the call and runs with no errors, but the next time I retrieve that group, the member is still there. I have tried using remove as well with much the same outcome.
I only want to remove the reference from the members collection, the User needs to persist. How do I persist the removal of the reference to a document?
It took me a while to discover this. If you have a group model (created using const Group = mongoose.model("Group", groupSchema)), and the id of the user you want to remove from the group, you can use the following syntax: const updatedGroup = await Group.findOneAndUpdate({ members: userId }, { $pull: { members: userId }}, {new: true, useFindAndModify: false});
The third parameter in that function indicates that the updated document should be returned, and ensures that you don't get a warning. Actually, that syntax will only work if the group to user relationship is one to many (i.e. any user can only be in one group). If the relationships is many to many, I guess you would have to use the following:
await Group.where({ members: userId}).update({ $pull: { members: userId }});
I have an array field called udids in Meteor.users schema, which should contains unique elements. This is how I defined the index using SimpleSchema and Collection2:
new SimpleSchema({
...
udids: {
type: Array,
index: true,
unique: true,
optional: true,
sparse: true,
},
'udids.$': {
type: String,
},
...
})
However, when I start the app, I got this error: E11000 duplicate key error collection: meteor.users index: c2_udids dup key: { : undefined }.
I tried searching for the documents with udids = undefined in the database: db.users.find({ udids: { $type: 6 } }) ($type: 6 for undefined value) but it returns nothing.
The error message is a bit unclear so I had to guess the reason why. I found out that the current database already has some users with udids = []. I'm writing a migration script to unset this field from those users. Hopefully this will help others who have the same problem as me.
I've not tested this, but it should ideally work.
Used Meteor.users as a collection name. You may want to replace it with whichever collection you want to run the validation against.
Made use of custom function to find at least one doc which contains the field's value in udids.
If you don't have access to the collection on the client side, then you can edit the custom function and have it handled asynchronously.
new SimpleSchema({
...
'udids': {
optional: true,
type: [String], // an array of string eg. ['A','B']
custom: function() {
// this.value = current field's value
// Below mongo query will check to see if there is at least one
// mongo doc whose udids contains the currently entered field's value.
// if found, then return the error message.
if (Meteor.users.findOne({
udids: {
$in: [this.value]
}
})) {
return "duplicateMsg";
}
},
...
});
SimpleSchema.messages({ duplicateMsg:"Udid already exists"});
If a user has an array called "tags":
var User = new Schema({
email: {
type: String,
unique: true,
required: true
},
tags: [{
type: mongoose.Schema.Types.ObjectId,
ref:'Tag',
required: true
}],
created: {
type: Date,
default: Date.now
}
});
and I do a populate('tags') on a query:
User.findById(req.params.id)
.populate("tags")
.exec(function(err, user) { ... });
If one of the tags in the list has actually been deleted, is there a way to remove this dead reference in "tags"?
Currently, the returned user object IS returning the desired result -- ie. only tags that actually exist are in the tags array... however, if I look at the underlying document in mongodb, it still contains the dead tag id in the array.
Ideally, I would like to clean these references up lazily. Does anyone know of a good strategy to do this?
I've tried to find some built-in way to do that but seems that mongoose doesn't provide such functionality.
So I did something like this
User.findById(userId)
.populate('tags')
.exec((err, user) => {
user.tags = user.tags.filter(tag => tag != null);
res.send(user); // Return result as soon as you can
user.save(); // Save user without dead refs to database
})
This way every time you fetch user you also delete dead refs from the document. Also, you can create isUpdated boolean variable to not call user.save if there was no deleted refs.
const lengthBeforeFilter = user.tags.length;
let isUpdated = user.tags.length;
user.tags = user.tags.filter(tag => tag != null);
isUpdated = lengthBeforeFilter > user.tags.length;
res.send(user);
if (isUpdated) {
user.save();
}
Assuming you delete these tags via mongoose, you can use the post middleware.
This will be executed after you've deleted a tag.
tagSchema.post('remove', function(doc) {
//find all users with referenced tag
//remove doc._id from array
});
its sample retainNullValues: true
Example:
User.findById(req.params.id)
.populate({
path: "tag",
options: {
retainNullValues: true
}
})
I am trying to update my collection which has an array field(initially blank) and for this I am trying this code
Industry.update({_id:industryId},
{$push:{categories: id:categoryId,
label:newCategory,
value:newCategory }}}});
No error is shown, but in my collection just empty documents({}) are created.
Note: I have both categoryId and newCategory, so no issues with that.
Thanks in advance.
This is the schema:
Industry = new Meteor.Collection("industry");
Industry.attachSchema(new SimpleSchema({
label:{
type:String
},
value:{
type:String
},
categories:{
type: [Object]
}
}));
I am not sure but maybe the error is occuring because you are not validating 'categories' in your schema. Try adding a 'blackbox:true' to your 'categories' so that it accepts any types of objects.
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [Object],
blackbox:true // allows all objects
}
}));
Once you've done that try adding values to it like this
var newObject = {
id: categoryId,
label: newCategory,
value: newCategory
}
Industry.update({
_id: industryId
}, {
$push: {
categories: newObject //newObject can be anything
}
});
This would allow you to add any kind of object into the categories field.
But you mentioned in a comment that categories is also another collection.
If you already have a SimpleSchema for categories then you could validate the categories field to only accept objects that match with the SimpleSchema for categories like this
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [categoriesSchema] // replace categoriesSchema by name of SimpleSchema for categories
}
}));
In this case only objects that match categoriesSchema will be allowed into categories field. Any other type would be filtered out. Also you wouldnt get any error on console for trying to insert other types.(which is what i think is happening when you try to insert now as no validation is specified)
EDIT : EXPLANATION OF ANSWER
In a SimpleSchema when you define an array of objects you have to validate it,ie, you have to tell it what objects it can accept and what it can't.
For example when you define it like
...
categories: {
type: [categoriesSchema] // Correct
}
it means that objects that are similar in structure to those in another SimpleSchema named categoriesSchema only can be inserted into it. According to your example any object you try to insert should be of this format
{
id: categoryId,
label: newCategory,
value: newCategory
}
Any object that isn't of this format will be rejected while insert. Thats why all objects you tried to insert where rejected when you tried initially with your schema structured like this
...
categories: {
type: [Object] // Not correct as there is no SimpleSchema named 'Object' to match with
}
Blackbox:true
Now, lets say you don't what your object to be filtered and want all objects to be inserted without validation.
Thats where setting "blackbox:true" comes in. If you define a field like this
...
categories: {
type: [Object], // Correct
blackbox:true
}
it means that categories can be any object and need not be validated with respect to some other SimpleSchema. So whatever you try to insert gets accepted.
If you run this query in mongo shell, it will produce a log like matched:1, updated:0. Please check what you will get . if matched is 0, it means that your input query is not having any matching documents.
Quick code:
var userSchema = new mongoose.Schema({
username: String,
password: {type: String, select: false}
});
userSchema.methods.checkPassword = function(password, done) {
console.log(password); // Password to check
console.log(this.password); // stored password
...
};
I don't want the password to be accessible by default, but I need a method to check against a user inputted password before authenticating the user. I know I can do a query to the DB to include these values, but I'm a bit lost on how I could access the hidden property on the schema method itself. this in the method itself is just the returned query, so it seems like it is inaccessible? Should I be doing the checkPassword() function elsewhere?
You can use select to select password in query. This is an example query.
User.findOne().select('password').exec(callback);
And this must be what you want to check password.
userSchema.methods.checkPassword = function(password, done) {
User.findOne({username: this.username}).select('password').exec(function (err, user) {
if (user.password == password)
return true;
else
return false;
});
}
I hope this might help you.
You can explicitly allow the password field (with {select:"false"}) to be returned in your find call with "+" operator before field e.g.:
User.findOne({}).select("+password") // "+" = allow select hidden field
A right way is writing the fields on method findOne. You can ask the fields that you want to return. In your case, it should be:
await User.findOne({ username: this.username }, 'password').exec();
Documentation:
mongoose.findOne
Above answers only show selection for a single property.
For multiple properties, syntax is this one:
await this.userModel
.findOne({ email }, { status: 1, firstName: 1, religion: 1 })
.exec();
This will return:
{
_id: new ObjectId("62de5a5158b809468b812345"),
status: 'Active',
firstName: 'John',
religion: 'Christian Orthodox'
}