update data in sub document without overwrite another field in mongoose - node.js

data in database
fieldA : 'aaaaa',
fieldB : {
en : 'aaaaaaaaaa',
de : 'bbbbbbbbbb'
}
new data
val = {
fieldA : 'aaaaa11',
fieldB : {
en : 'aaaaa1111'
}
}
i tried this code
Model.findOneAndUpdate({fieldA : val.fieldA},{ $set : val})
when i run this command 'fieldB.de' is missing. i want to know how to update as result seem below
fieldA : 'aaaaa11',
fieldB : {
en : 'aaaaa1111',
de : 'bbbbbbbbbb'
}

Try this:
Model.findOneAndUpdate({fieldA : val.fieldA},{
"$set": {
"fieldA": val.fieldA,
"fieldB.en": val.fieldB.en
}
})
In your solution you update document completny with other one. To update only specific fields you must specify this fields in "$set" object

I resolve this by finding the document first and editing de object manually.
/* Recursive function to update manually an object */
const update = (obj, newValues) => {
Object.entries(newValues).forEach( ([key, value]) => {
if(typeof value === 'object'){
update(obj[key], value);
} else {
obj[key] = value;
}
});
}
/* Find document, update and save */
const infoToUpdate = {};
const doc = await Model.findOne({ field: valueToFind });
if (doc) {
object.update(doc, infoToUpdate);
doc.save();
}

You have to change the object passed to findOneAndUpdate() so that you don't pass fieldB as an object, only the fields to update.
It can be done with something like this:
let val = {
fieldA: 'aaaaa11',
fieldB: {
en: 'aaaaa1111',
},
};
if (val.fieldB) {
const newFieldB = Object.entries(val.fieldB).reduce(
(a, [key, value]) => ({ ...a, ['fieldB.' + key]: value }),
{}
);
delete val.fieldB;
val = { ...val, ...newFieldB };
}
Model.findOneAndUpdate({fieldA : val.fieldA},{ $set : val})
With that, the value of val passed to findOneAndUpdate() is:
{
fieldA: 'aaaaa11',
'fieldB.en': 'aaaaa1111',
}
Instead of:
{
fieldA: 'aaaaa11',
fieldB: {
en: 'aaaaa1111',
},
}
... and attributes of fieldB that you didn't specify in val but that were in your DB will not get overridden.

Related

How to delete an object from an array in mongodb?

MongoDB collection/doc :
{
_id:something,
name:something,
todos: [
{key:1234},
{key:5678}
]
}
I want to delete the object with key:5678 using mongoose query. I did something like this but It's not deleting the object at all and returning the User with unchanged todos array.
Node Route:
router.post('/:action', async (req, res) => {
try {
if (req.params.action == "delete") {
const pullTodo = { $pull: { todos: { key: 5678 } } }
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo)
if (todo) {
res.json({ msg: "Todo Deleted", data: todo });
}
}
} catch (err) {
console.log(err)
}
})
I have allso tried findByIdAndUpdate(),update() methods but none of them deleting the object from the array. Getting User as a result without deleting the object from the array.
It is working, but you forgot give an configuration to the function call of Model.findByIdAndUpdate..
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo, {new: true});
// if {new: true} is enabled, then it will give the latest & updated document from the
// result of the query. By default it gives the previous document.
Do some, research first. This isn't a type of question that should be asked. It's already been answered several times in stackoverflow.
Try using Model.findOneAndRemove() instead. It also makes only one call to the database.
Example: User.findOneAndRemove({'todos':{'$elemMatch':{key}});
can you please re-visit your JSON like below and see if this design works for you.
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234, 5678 ] } }
> db.test6.update({},{$pull:{"todos1.key1":5678}},{multi:true});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234 ] } }
>
I was adding a key property to every "Todo" using "mongoose.Types.ObjectId()" and I was querying with id string like : "5f439....." which was the problem. So I used:
1st Step: MongoId = require('mongodb').ObjectID;
2nd Step: const key = MongoId (**<actual id here>**);

Add object to array of objects if object doesn't exist, otherwise update object

Desired Behaviour
Add object to array of objects if it doesn't exist, otherwise update object.
Each user can only add one rating.
Schema
"ratings" : [
{
"user_email" : "john#smith.com",
"criteria_a" : 0,
"criteria_b" : 3,
"criteria_c" : 1,
"criteria_d" : 5,
"criteria_e" : 2
},
{
"user_email" : "bob#smith.com",
"criteria_a" : 1,
"criteria_b" : 3,
"criteria_c" : 5,
"criteria_d" : 2,
"criteria_e" : 1
},
{
"user_email" : "jane#smith.com",
"criteria_a" : 5,
"criteria_b" : 3,
"criteria_c" : 1,
"criteria_d" : 0,
"criteria_e" : 1
}
]
What I've Tried
I've read this:
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
Source: https://docs.mongodb.com/manual/reference/operator/update/addToSet/
But I'm a bit confused because I think I am trying to:
$addToSet if object doesn't exist
$set (ie update the existing object) if it does exist
The following keeps adding rating objects to the array, even if a user has already added a rating.
var collection = mongo_client.db("houses").collection("houses");
var o_id = new ObjectID(id);
var query = { _id: o_id, "ratings.user_email": user_email };
var update = { $addToSet: { ratings: rating } };
var options = { upsert: true };
collection.updateOne(query, update, options, function(err, doc) {
if (err) {
res.send(err);
} else {
res.json({ doc: doc })
}
});
Edit:
The following works if a user has already submitted a rating, but otherwise it throws the error The positional operator did not find the match needed from the query.:
var update = { $set: { "ratings.$": rating } };
You will need to perform this with two distinct actions:
Attempt a $push if no rating for the given user exists yet, i.e. negate the user_email in your match portion of the update query as you're already doing.
Perform a second update once again matching on "ratings.user_email": user_email, but this time perform a $set on "ratings.$": rating.
If no rating for the user exists yet, then (1) will insert it, otherwise no operation occurs. Now that the rating is guaranteed to exist, (2) will ensure that the rating is updated. In the case that (1) inserted a new rating, (2) will simply result in no operation occuring.
The result should look something like this:
collection.updateOne(
{ _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } },
{ $push: { ratings: rating } }
);
collection.updateOne(
{ _id: o_id, "ratings.user_email": user_email },
{ $set: { "ratings.$": rating } }
);
A more verbose example with comments is below:
// BEGIN new rating query/update variables
var query_new_rating = { _id: o_id, ratings: { $not: { $elemMatch: { user_email: user_email } } } };
var update_new_rating = { $push: { ratings: rating } };
// END new rating query/update variables
// part 01 - attempt to push a new rating
collection.updateOne(query_new_rating, update_new_rating, function(error, result) {
if (error) {
res.send(error);
} else {
// get the number of modified documents
// if 0 - the rating already exists
// if 1 - a new rating was added
var number_modified = result.result.nModified;
// BEGIN if updating an existing rating
if (number_modified === 0) {
console.log("updating existing rating");
// BEGIN existing rating query/update variables
var query_existing_rating = { _id: o_id, "ratings.user_email": user_email };
var update_existing_rating = { $set: { "ratings.$": rating } };
// END existing rating query/update variables
// part 02 - update an existing rating
collection.updateOne(query_existing_rating, update_existing_rating, function(error, result) {
if (error) {
res.send(error);
} else {
console.log("updated existing rating");
res.json({ result: result.result })
}
});
}
// END if updating an existing rating
// BEGIN if adding a new rating
else if (number_modified === 1) {
console.log("added new rating");
res.json({ result: result.result })
}
// END if adding a new rating
}
});

Multiple insertion with addition data with pg-promise

I have a large dataset that I want to insert into a postgres db, I can achieve this using pg-promise like this
function batchUpload (req, res, next) {
var data = req.body.data;
var cs = pgp.helpers.ColumnSet(['firstname', 'lastname', 'email'], { table: 'customer' });
var query = pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
// success;
})
.catch(error => {
// error;
return next(error);
});
}
The dataset is an array of objects like this:
[
{
firstname : 'Lola',
lastname : 'Solo',
email: 'mail#solo.com',
},
{
firstname : 'hello',
lastname : 'world',
email: 'mail#example.com',
},
{
firstname : 'mami',
lastname : 'water',
email: 'mami#example.com',
}
]
The challenge is I have a column added_at which isn't included in the dataset and cannot be null. How do I add a timestamp for each record insertion to the query.
As per the ColumnConfig syntax:
const col = {
name: 'added_at',
def: () => new Date() // default to the current Date/Time
};
const cs = pgp.helpers.ColumnSet(['firstname', 'lastname', 'email', col], { table: 'customer' });
Alternatively, you can define it in a number of other ways, as ColumnConfig is very flexible.
Example:
const col = {
name: 'added_at',
mod: ':raw', // use raw-text modifier, to inject the string directly
def: 'now()' // use now() for the column
};
or you can use property init to set the value dynamically:
const col = {
name: 'added_at',
mod: ':raw', // use raw-text modifier, to inject the string directly
init: () => {
return 'now()';
}
};
See the ColumnConfig syntax for details.
P.S. I'm the author of pg-promise.

Different behavior between mongo and mongoose?

In the mongo shell I can do this:
db.getCollection('usercourses').update({
_id:ObjectId("54bee7c6ababf28b4ea5a07f")},
{
$unset:{
'steps.0.topic':''
}
},
{strict:false})
And it'll delete the "topic" field from the object inside the array, from the document found.
But when I do this in mongoose:
function() {
return UserCourses.findQ()
.then(function(uCourse){
return Q.all(uCourse.map(worker))
}).catch(function(error) {
console.log(error);
});
}
function worker(uCourse) {
return Q.all(uCourse.steps.map(
function(step,i){
var field1 = 'steps.'+i+'.topic';
return UserCourses.updateQ({_id:uCourse._id},
{
$unset:{field1:''},
},
{strict:false});
}
))
}
Nothing happens.
Why?
Because you're trying to unset a field called field1 (because ES5 doesn't have computed property names):
$unset : { field1 : '' }
Instead, you need to do this:
var obj = {};
obj['steps.'+i+'.topic'] = '';
...
$unset : obj

sequelize dynamic query params

I'm sending query params as JSON format in req.query.p from my front-end MVC framework , the point is that this could be a dynamic key and value, for example:
req.query.p = {nombre : 'juan'}
or
req.query.p = {pais : 'chile'}
So I need the key, and the value to put them in the where statement, something like this
exports.select = function(req, res){
console.log('=> GET | Obtener peliculas'.bold.get);
db.Pelicula
.findAndCountAll({
limit : req.query.limit,
offset : req.query.offset,
where : req.query.p ? [req.query.p.KEY + " = ?", req.query.p.VAL] : null
})
.success(function(resp){
console.log(JSON.stringify(resp.rows, null, 4).bold.get);
res.json({peliculas : resp.rows, meta : { total : resp.count}});
});
}
The where parameter can be an object, so you can just pass where: req.query.p
Usually I put the entire object, so if it comes empty, it will work normally as if there is no conditional WHERE.
You don't need to add {} in the where, because the object that comes from req.query already has it.
const filter = req.query;
example= await ModelExample.findAndCountAll({
where:
filter
})
With ES6 and with usage of the dynamic properties I'll do it like this
const { Op } = require("sequelize");
const from = new Date()
// const to = new Date().setMinutes(40)
const to = null
let where = {
timestamp: {
[Op.or]: {}
}
}
if (from) {
where.timestamp[Op.or][Op.gte] = new Date(from)
}
if (to) {
where.timestamp[Op.or][Op.lte] = new Date(to)
}
console.log(where);
Model.find({ where })

Resources