MongoDB, update collection field if new value is not null - javascript

I would update a collection setting the value only if the new values are not null.
I have a code like this:
...
var userName = req.body.nome;
var userSurname = req.body.cognome;
var userAddress = req.body.indirizzo;
collection.update(
{_id:ObjectId(req.session.userID)},
{$set: { nome: userName, cognome: userSurname, indirizzo: userAddress }}
)
Is there an easy way for doing this?
ANOTHER WAY:
if I could take the value req.body.* from the placeholder of the form where I take the data, I could solve the problem.. but is this possible?

You could try something like this:
var objForUpdate = {};
if (req.body.nome) objForUpdate.nome = req.body.nome;
if (req.body.cognome) objForUpdate.cognome = req.body.cognome;
if (req.body.indirizzo) objForUpdate.indirizzo = req.body.indirizzo;
//before edit- There is no need for creating a new variable
//var setObj = { $set: objForUpdate }
objForUpdate = { $set: objForUpdate }
collection.update({_id:ObjectId(req.session.userID)}, objForUpdate )

You can use lodash like this other question: https://stackoverflow.com/a/33432857/4777292
_.pickBy({ a: null, b: 1, c: undefined }, _.identity);
would be
{b:1}

Are you not just asking to pass in all the fields that you posted? Why not do this then?
(And basically just a cut and paste of your code):
collection.update(
{_id: ObjectId(req.session.userID)},
{$set: req.body }
)
Then whatever content you posted as fields is set within your update.
Note that use of set will only overwrite, or add new fields. If you just want to replace the whole document, then remove the whole {$set: (..) } notation and just pass in req body as it's a valild object.

You can use mongoose for that by casting req.body to your model,
I assume you have mongoose model called User, and in your controller,
var userModel = new User(req.body);
User.update({_id: req.session.userID}, userModel, {upsert: true}, function(err){
console.log("Error occured!");
});
There is no mongoose tag, but I strongly recomment to use that. For more details;
Mongoose Update
Mongoose Model

If there is not any field that you dont want users to be able to change. since this method will take any value which is not empty and update it. you can do it like this.
const updatedFields = {};
Object.keys(req.body).forEach(key => {
if (!isEmpty(req.body[key])) {
updatedFields[key] = req.body[key];
}
});
YourModel.findOneAndUpdate(
{ employee_id: req.body.employee_id },
{$set:updatedFields},
{new:true}).then(updatedObj =>{
console.log("updated obj:",updatedObj);
})
is empty function
const isEmpty = value =>
value === undefined ||
value === null ||
(typeof value === "object" && Object.keys(value).length === 0) ||
(typeof value === "string" && value.trim().length === 0);

This version still allows for null string fields to be respected and updated. Omitted fields would be ignored.
const cleanedObject = Object.keys(origObject).reduce((acc, k) => {
if (typeof origObject[k] === "undefined") return acc;
acc[k] = origObject[k];
return acc;
}, {});
collection.update({_id:ObjectId(req.session.userID)}, cleanedObject })

Probably you've got already user authenticated so you should have req.user.*
in this case you can use ternary operator to assign the value and update it with either new one or the current one (so there is no update)
var userName = req.body.nome ? req.body.nome : req.user.nome;
var userSurname = req.body.cognome ? req.body.nome : req.user.cognome;
var userAddress = req.body.indirizzo ? req.body.indirizzo : req.user.indirizzo;
collection.update(
{_id:ObjectID(req.session.userID)},
{$set: { nome: userName, cognome: userSurname, indirizzo: userAddress }}
)
If you don't have req.user then you can do it in 3 steps.
1. find user in collection
2. get current data
3. update data with new or current (as above)
let currentName
let currentSurname
db. collection.findOne({_id: ObjectID(req.session.userID)}, (err, user) => {
if (err) { } // handle error here
else if (user) {
currentName = user.name
currentSurname = user.surname
}
})

let objForUpdate = {}
for (const key of Object.keys(req.body)) {
if (req.body[key]) objForUpdate = Object.assign({}, objForUpdate, { [key]: req.body[key] })
}
collection.update({_id:ObjectId(req.session.userID)}, { $set: objForUpdate })
This will dynamically add fields in objForUpdate if defined in the body.

var userName = req.body.nome;
var userSurname = req.body.cognome;
var userAddress = req.body.indirizzo;
collection.update(
{ _id: ObjectId(req.session.userID) },
{
$set: {
...userName && { nome: userName },
...userSurname && { cognome: userSurname },
...userAddress && { indirizzo: userAddress },
},
}
)

The answer by Hüseyin BABAL is on the right track, but that will generate a warning from mongo because calling new User() will create a new _id which is immutable. What you want to do is the following:
const userModel = Object.assign(req.body);
User.update({_id: req.session.userID}, userModel, {upsert: true},
function(err){
console.log("Error occured!");
});

Related

mongoose check if id exists but that id is nested inside an array

When i fetch new alerts, i want to check if the ID of the new alert was already recorded. The issue is that that ID is nested inside an array. There's the alertsDetails array, which contains objects and those objects have an _ID filed which is what i want to check. I am not sure how to achieve that. I got the code below but then i have to iterate over the result to check the exists value. Im sure there must be a better way.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const G2AlertsSchema = new Schema(
{
status: { type: String, required: true },
openDate: { type: Date, required: true },
alertType: { type: Array, required: true },
severity: { type: Array, required: true },
locationName: { type: Array, required: true },
history: { type: Array, required: true },
alertDetails: { type: Array, required: false },
assignedTo: { type: Schema.Types.ObjectId, ref: 'user' },
},
{
timestamps: true,
},
);
const G2Alerts = mongoose.model('G2Alert', G2AlertsSchema);
module.exports = G2Alerts;
This is the code i found on mongodb's website. I just want to see if the ID exists only. Basically when i fetch the new alerts i get an array and i iterate over it, i want to check each item's ID against what's inside the Database. If it's there, skip and go to the next. If it's new, then create a new alert and save it.
const exists = await G2Alerts.aggregate([
{
$project: {
exists: {
$in: ['5f0b4f508bda3805754ab343', '$alertDetails._id'],
},
},
},
]);
EDIT: Another thing. I am getting a eslint warning saying i should use array iteration instead of a for loop. The issue is, i need to use await when looking up the Alert ID. If i use, reduce or filter, i can't use await. If i use async inside the reduce or filter function, then it will return promises in or just an empty array.
This below works, based on the answer provided by Tom Slabbaert
const newAlertsData = [];
for (let item of alertData.data.items) {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
newAlertsData.push(item);
}
}
if (newAlertsData.length !== 0) {......
But this does not
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
const exists = await G2Alerts.find({ 'alertDetails._id': `${item._id}` });
if (exists.length === 0) {
filtered.push(item);
}
return filtered;
}, []);
You're not far off, here is an example using the correct syntax:
const exists = await G2Alerts.findOne({"alertDetails._id": '5f0b4f508bda3805754ab343'}});
if (!exists) {
... do something
}
This can also be achieve using aggregate with a $match stage instead of a $project stage or even better countDocuments which just returns the count instead of the entire object if you do not require it.
One more thing I'd like to add is that make sure alertDetails._id is string type as you're using string in you're $in. otherwise you'll need to cast them to ObjectId type in mongoose like so:
new mongoose.Types.ObjectId('5f0b4f508bda3805754ab343')
And for Mongo:
import {ObjectId} from "mongodb"
...
new ObjectId('5f0b4f508bda3805754ab343')
EDIT
Try something like this?
let ids = alertData.data.items.map(item => item._id.toString());
let existing = await G2Alerts.distinct("alertsDetails._id", {"alertsDetails._id": {$in: ids}});
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString())) {
return [item].concat(filtered)
}
return filtered;
}, []);
This way you only need to call the db once and not multiple times.
Final code based on the provided answer.
const ids = alertData.data.items.map(item => item._id);
const existing = await G2Alerts.find({ 'alertDetails._id': { $in: ids } }).distinct(
'alertDetails._id',
(err, alerts) => {
if (err) {
res.send(err);
}
return alerts;
},
);
const filteredAlerts = alertData.data.items.reduce((filtered, item) => {
if (!existing.includes(item._id.toString()) && item.openDate > dateLimit) {
return [item].concat(filtered);
}
return filtered;
}, []);

cloud firestore only add object if its name is unique

Object1 looks like this { category1: '', category2: '', description: '' }
Category1 has an array field for [category2] and a Name
My firebase contains 3 documents: 1 for object1 1 for Category1 and the last one is not relevant.
In my Ionic application I have made an input field where the user can paste an array of JSON objects. these JSON objects get converted to Object1 and added to the database.
My problem is when category1 is new it should be added to the firebase document for category1 but can't be a duplicate. Or when category2 is new it should update category1.
I think the solution is to make category1 unique Id its property name, so I can check with firebase security rules if it already exists when trying to add it.
Can someone explain to me how to set name as the unique Id of this document or does anyone know a better solution?
in my service:
private category1Collection: AngularFirestoreCollection<Category1>;
private category1s: Observable<Category1[]>;
constructor(db: AngularFirestore) {
this.category1Collection = db.collection<category1>('category1s');
var docRef = db.collection("category1s").doc("category1s");
this.category1s= this.category1Collection.snapshotChanges().pipe(
map(actions => {
return actions.map(a => {
const data = a.payload.doc.data();
const id = a.payload.doc.id;
return { id, ...data };
})
})
)
}
addItem(category1: Category1) {
return this.category1Collection.add(category1);
}
I fixed it:
changed code in the service to:
addItem(category1: Category1) {
return this.category1Collection.doc(category1.Naam).set(category1);
}
and this code in the page.ts:
this.category1Service.getItem(HF.Naam).subscribe(data => {
if (data != null) {
console.log(data)
HF.category2s = data.category2s;
if (!this.containsCategory2(element.category2, HF.category2s)) {
HF.category2s.push(element.category2);
this.category1Service.addItem(HF);
}
} else {
this.category1Service.addItem(HF);
}
});
containsCategory2(category2: string, list: string[]) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === category2) {
return true;
}
}
return false;
}

Save with firebase with a dynamic key name

I am trying to insert in my database the month chosen by the user.
saveInBdd (){
this.errors = [];
const user = firebase.auth().currentUser;
user.updateProfile({
displayName: this.firstname,
}).then(()=>{
this.saveUserToUsersRef(user)
}, error => {
this.errors.push(error.message)
})
},
saveUserToUsersRef(user){
return this.usersRef.child(user.uid).update({
tab1: { this.months[0].selectedOption : "test"}
})
},
This code returns this error to me:
The property name of a JSON object in the notation you're using needs to be a literal. To use a variable as the name of a property, use [] notation:
saveUserToUsersRef(user){
var updates = {};
updates[this.months[0].selectedOption] = "test";
return this.usersRef.child(user.uid).update(updates))
},

mongoose | update all documents according to a field in each document

i have a collection which i would like to update all of it's documents according to field email and convert it to lower case.
const addressBookSchema = new Schema({
email: String,
});
const addressBook = mongoose.model("address_book", addressBookSchema)
i'm trying to do the following:
addressBook.update({}, {$set: {email: email.toLowerCase()}}, {multi: true});
But that doesn't work.
how do i get the email field and set it to lowercase?
By the below method you are able to update multiple document with lower case aggregate function.
db.addressBook.updateMany(
{},
[{$set : {email :{ $toLower: "$email" } }}],
)
For doing it on all documents,
addressBook.find({}, {email: 1})
.exec((err, docs) => {
if (err || docs == undefined || docs.length == 0)
;
else {
docs.forEach((doc) => {
addressBook.findOneAndUpdate({_id: doc._id},
{$set: {email: doc.email.lowercase()}})
.exec();
});
}
});
If you have such a large dataset, you should use the bulkWrite() function
addressBook.bulkWrite([
{
updateMany: {
filter: {},
update: { email: email.lowercase()}
}
},
]).then(handleResult);

Mongodb: dynamically exclude fields from update function [duplicate]

I would update a collection setting the value only if the new values are not null.
I have a code like this:
...
var userName = req.body.nome;
var userSurname = req.body.cognome;
var userAddress = req.body.indirizzo;
collection.update(
{_id:ObjectId(req.session.userID)},
{$set: { nome: userName, cognome: userSurname, indirizzo: userAddress }}
)
Is there an easy way for doing this?
ANOTHER WAY:
if I could take the value req.body.* from the placeholder of the form where I take the data, I could solve the problem.. but is this possible?
You could try something like this:
var objForUpdate = {};
if (req.body.nome) objForUpdate.nome = req.body.nome;
if (req.body.cognome) objForUpdate.cognome = req.body.cognome;
if (req.body.indirizzo) objForUpdate.indirizzo = req.body.indirizzo;
//before edit- There is no need for creating a new variable
//var setObj = { $set: objForUpdate }
objForUpdate = { $set: objForUpdate }
collection.update({_id:ObjectId(req.session.userID)}, objForUpdate )
You can use lodash like this other question: https://stackoverflow.com/a/33432857/4777292
_.pickBy({ a: null, b: 1, c: undefined }, _.identity);
would be
{b:1}
Are you not just asking to pass in all the fields that you posted? Why not do this then?
(And basically just a cut and paste of your code):
collection.update(
{_id: ObjectId(req.session.userID)},
{$set: req.body }
)
Then whatever content you posted as fields is set within your update.
Note that use of set will only overwrite, or add new fields. If you just want to replace the whole document, then remove the whole {$set: (..) } notation and just pass in req body as it's a valild object.
You can use mongoose for that by casting req.body to your model,
I assume you have mongoose model called User, and in your controller,
var userModel = new User(req.body);
User.update({_id: req.session.userID}, userModel, {upsert: true}, function(err){
console.log("Error occured!");
});
There is no mongoose tag, but I strongly recomment to use that. For more details;
Mongoose Update
Mongoose Model
If there is not any field that you dont want users to be able to change. since this method will take any value which is not empty and update it. you can do it like this.
const updatedFields = {};
Object.keys(req.body).forEach(key => {
if (!isEmpty(req.body[key])) {
updatedFields[key] = req.body[key];
}
});
YourModel.findOneAndUpdate(
{ employee_id: req.body.employee_id },
{$set:updatedFields},
{new:true}).then(updatedObj =>{
console.log("updated obj:",updatedObj);
})
is empty function
const isEmpty = value =>
value === undefined ||
value === null ||
(typeof value === "object" && Object.keys(value).length === 0) ||
(typeof value === "string" && value.trim().length === 0);
This version still allows for null string fields to be respected and updated. Omitted fields would be ignored.
const cleanedObject = Object.keys(origObject).reduce((acc, k) => {
if (typeof origObject[k] === "undefined") return acc;
acc[k] = origObject[k];
return acc;
}, {});
collection.update({_id:ObjectId(req.session.userID)}, cleanedObject })
Probably you've got already user authenticated so you should have req.user.*
in this case you can use ternary operator to assign the value and update it with either new one or the current one (so there is no update)
var userName = req.body.nome ? req.body.nome : req.user.nome;
var userSurname = req.body.cognome ? req.body.nome : req.user.cognome;
var userAddress = req.body.indirizzo ? req.body.indirizzo : req.user.indirizzo;
collection.update(
{_id:ObjectID(req.session.userID)},
{$set: { nome: userName, cognome: userSurname, indirizzo: userAddress }}
)
If you don't have req.user then you can do it in 3 steps.
1. find user in collection
2. get current data
3. update data with new or current (as above)
let currentName
let currentSurname
db. collection.findOne({_id: ObjectID(req.session.userID)}, (err, user) => {
if (err) { } // handle error here
else if (user) {
currentName = user.name
currentSurname = user.surname
}
})
let objForUpdate = {}
for (const key of Object.keys(req.body)) {
if (req.body[key]) objForUpdate = Object.assign({}, objForUpdate, { [key]: req.body[key] })
}
collection.update({_id:ObjectId(req.session.userID)}, { $set: objForUpdate })
This will dynamically add fields in objForUpdate if defined in the body.
var userName = req.body.nome;
var userSurname = req.body.cognome;
var userAddress = req.body.indirizzo;
collection.update(
{ _id: ObjectId(req.session.userID) },
{
$set: {
...userName && { nome: userName },
...userSurname && { cognome: userSurname },
...userAddress && { indirizzo: userAddress },
},
}
)
The answer by Hüseyin BABAL is on the right track, but that will generate a warning from mongo because calling new User() will create a new _id which is immutable. What you want to do is the following:
const userModel = Object.assign(req.body);
User.update({_id: req.session.userID}, userModel, {upsert: true},
function(err){
console.log("Error occured!");
});

Categories