mongoose update nested document not working - javascript

I try to figure out what I'm doing wrong that moongose is not updating my document. Just for testing the id's are hardcoded.
node.js
var id1 = "592fd471fedd311d5c76a024";
var id2 = "592fd4ad608e001d79938ba8";
Workshop.update({
_id: id1,
'themen._id': id2
}, {
'$set': {
'themen.$.risikolandschaft': ["John", "Doe"],
}
},
function(err, body) {
if (err) {
console.log(err)
} else {
console.log(body);
}
}
);
here my data copied from MongoDB Compass
{
"_id" : ObjectId("592fd471fedd311d5c76a024"),
"clientid" : "592cff8794738f0347609666",
"bezeichnung" : "Workshop 1.5.2017 / 10:46",
"themen" : [
{
"thema" : {
"__v" : 0,
"bezeichnung" : "Sturm",
"beschreibung" : "Text",
"_id" : "59255757b1485d0ad2a6924f"
},
"risikolandschaft" : [ "one", "two", "three"],
"date" : "2017-06-01T08:47:41.944Z",
"_id" : ObjectId("592fd4ad608e001d79938ba8")
}
], ...
the log from body
{ n: 0, nModified: 0, ok: 1 }
for adding a new item to the "themen" subdocument i use this:
var new_thema = {
_id: mongoose.Types.ObjectId(),
date : req.body.date,
risikolandschaft : req.body.risikolandschaft,
thema : req.body.thema };
here the mongoose schema
var WorkshopSchema = mongoose.Schema({
clientid: {
type: String,
required: true
},
bezeichnung: {
type: String,
required: true
},
stammdaten : [],
date : Date,
themen : []
});
and there is no subschema for "themen"
"mongoose": "^4.8.6"

Related

Update Many if exists , otherwise create for each LeadId that doesn't exists a new Document

Consider the Schema :
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const EightWeekGamePlanSchema = new Schema({
Week: {
type: Number,
required: true
},
LeadId: {
type: String,
required: true
},
PackageId: {
type: String,
required: true
},
BusinessName: {
type: String,
required: true
},
PhoneNumberMasque: {
type: String,
required: true
},
City: {
type: String,
required: true
},
Rooms: {
type: Number,
required: true
},
LeadStartDate: {
type: Date
},
LeadEndDate: {
type: Date
},
TargetedToBeClaimedByClientType: {
type: Number,
required: true
},
TotalClaimsLeftToBeClaimedByClientType: {
// incresed by 1 every time it's claimed
type: Number,
required: true
},
TotalClaimsToBeClaimedByClientType: {
// Stays fixed
type: Number,
required: true
},
Status: {
type: Number,
required: true
},
InsertDate: {
type: Date,
default: Date.now
}
});
module.exports = EightWeekGamePlan = mongoose.model(
"eightweekgameplan",
EightWeekGamePlanSchema
);
I'm trying the write a pretty complex query : updates multiple documents by given number in howManyClaims , if their LeadID appears in a given array winnerLeads AND their TargetedToBeClaimedByClientType property equals the given groupTarget :
router.post("/add-claims-to-group", auth, async (req, res) => {
const { howManyClaims, groupTarget, winnerLeads, week } = req.body;
EightWeekGamePlan.updateMany(
{
LeadId: {
$in: winnerLeads
},
TargetedToBeClaimedByClientType: groupTarget
},
{ $inc: { TotalClaimsToBeClaimedByClientType: howManyClaims } },
{ multi: true },
(err, writeResult) => {
if (err) {
console.log(err);
} else {
console.log(writeResult);
}
}
);
}
Otherwise , do some kind of a Create : take one of the existing EightWeekGamePlan documents that belong to the LeadID , duplicate it and set its TotalClaimsToBeClaimedByClientType property with howManyClaims.
Is it possible to combine both Update and Create actions in one query ?
From your query, since you're doing .updateMany(), you don't have to do { multi: true }. Anyway usually you can do upsert using {upset: true}, but it would ideally create a new document based on filter criteria with update fields from input query only if no match is found in DB. But since here we're have a list ($in) in filter criteria it might not work normally, try this :
let winnerLeads = [1, 2, 3, 31, 5]
let groupTarget = 1
let howManyClaims = 2
let bulkArr = []
for (i of winnerLeads) {
bulkArr.push({
updateOne: {
"filter": {
LeadId: i,
TargetedToBeClaimedByClientType: groupTarget
},
// If you wanted it to be incremented rather than replace the field, then try `$inc` instead of `$set`.
"update": { $set: { TotalClaimsToBeClaimedByClientType: howManyClaims } },
"upsert": true
}
})
}
db.EightWeekGamePlan.bulkWrite(bulkArr);
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e06eb8f400289966e00fac2"),
"LeadId" : 1,
"TotalClaimsToBeClaimedByClientType" : 1.0,
"TargetedToBeClaimedByClientType" : 1
}
/* 2 */
{
"_id" : ObjectId("5e06eb98400289966e00fb88"),
"LeadId" : 2,
"TotalClaimsToBeClaimedByClientType" : 1.0,
"TargetedToBeClaimedByClientType" : 1
}
/* 3 */
{
"_id" : ObjectId("5e06eba0400289966e00fc47"),
"LeadId" : 3,
"TotalClaimsToBeClaimedByClientType" : 0,
"TargetedToBeClaimedByClientType" : 11
}
/* 4 */
{
"_id" : ObjectId("5e06ebac400289966e00fd4b"),
"LeadId" : 4,
"TotalClaimsToBeClaimedByClientType" : 1,
"TargetedToBeClaimedByClientType" : 11
}
/* 5 */
{
"_id" : ObjectId("5e06ecef400289966e01273a"),
"LeadId" : 5,
"TotalClaimsToBeClaimedByClientType" : 1.0,
"TargetedToBeClaimedByClientType" : 1
}
Result :
/* 1 */
{
"_id" : ObjectId("5e06eb8f400289966e00fac2"),
"LeadId" : 1,
"TotalClaimsToBeClaimedByClientType" : 2.0,
"TargetedToBeClaimedByClientType" : 1
}
/* 2 */
{
"_id" : ObjectId("5e06eb98400289966e00fb88"),
"LeadId" : 2,
"TotalClaimsToBeClaimedByClientType" : 2.0,
"TargetedToBeClaimedByClientType" : 1
}
/* 3 */
{
"_id" : ObjectId("5e06eba0400289966e00fc47"),
"LeadId" : 3,
"TotalClaimsToBeClaimedByClientType" : 0,
"TargetedToBeClaimedByClientType" : 11
}
/* 4 */
{
"_id" : ObjectId("5e06ebac400289966e00fd4b"),
"LeadId" : 4,
"TotalClaimsToBeClaimedByClientType" : 1,
"TargetedToBeClaimedByClientType" : 11
}
/* 5 */
{
"_id" : ObjectId("5e06ecef400289966e01273a"),
"LeadId" : 5,
"TotalClaimsToBeClaimedByClientType" : 2,
"TargetedToBeClaimedByClientType" : 1
}
/* 6 */
{
"_id" : ObjectId("5e071eb1400289966e0597a0"),
"TargetedToBeClaimedByClientType" : 1.0,
"LeadId" : 3.0,
"TotalClaimsToBeClaimedByClientType" : 2.0
}
/* 7 */
{
"_id" : ObjectId("5e071e62400289966e059168"),
"TargetedToBeClaimedByClientType" : 1.0,
"LeadId" : 31.0,
"TotalClaimsToBeClaimedByClientType" : 2.0
}
Basically bulkWrite doesn't return any documents except write result, you can verify in DB for update operation result, Also from the above result 6 got insert as LeadId : 3 + TargetedToBeClaimedByClientType" : 1.0(So LeadId:3 is duplicated) combination is not present in DB & 7 got inserted as LeadId : 31 is not present in DB, Remaining 1,2,5's TotalClaimsToBeClaimedByClientType got updated.
Ref : bulkWrite

Editing a value of an object in an array

I have this object:
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877a8"),
"websites" : [
"",
"",
""
],
"keys" : [
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877af"),
"name" : "Google",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877ae"),
"name" : "Built With",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877ad"),
"name" : "Check Host",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877ac"),
"name" : "Alexa",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877ab"),
"name" : "Facebook",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877aa"),
"name" : "Instagram",
"value" : ""
},
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877a9"),
"name" : "Moz",
"value" : ""
}
],
"username" : "admin#admin",
"isPremium" : false,
"accType" : "admin",
"hash" : "very long hash",
"salt" : "long salt",
}
Now. Using NodeExpress and Mongoose I need to be able to edit the value field inside of every object inside the keys array.
My GET operation is this:
// GET: /websites/:_id - show edit form
router.get('/keys/edit/:_id', isAdmin, function(req, res, next) {
// console.log('tada');
// console.log(req.params._id);
Account.findOne({ _id: req.user._id }, function(err, user) {
var selectedKey = findById(user.keys, req.params._id);
// var keys = user.keys.findOne(req.params._id);
console.log(selectedKey);
res.render('admin/edit', {
title: 'Edit websites',
user: req.user,
value: selectedKey.value,
});
});
});
How the app works is: The admin logs in. He sees all users and chooses which one he wants to modify, then admin sees all keys. I will attach screenshots to explain it more clearly.
Now. I think I know what I need to do, but I have no clue how to translate it to code.
I think I need to: Find the index of the array element, like in the GET request, update the value with the posted value. I think I need to find the index in the array.
But as I said I have no clue how to do it.
My POST looks like this right now:
// POST: /keys/edit/_id - save updates
router.post('/keys/edit/:_id', isAdmin, function(req, res, next) {
var p = req.params;
var b = req.body;
Account.findOne({ _id: req.user._id }, function(err, user) {
var selectedKey = findById(user.keys, req.params._id);
// console.log('Key value: ' + req.body.keyValue);
// console.log('Selected key: ' + selectedKey);
console.log('id:' + req.params._id);
if (err) {
console.log(err);
} else {
console.log(user);
user.keys.set(req.params._id, req.body.keyValue);
user.save(err => {
if (err) {
console.log(err);
} else {
console.log('all good');
}
res.redirect('/admin');
});
}
});
EDIT: So I was working on it for a while now and I figured out this. I am using the correct user, I am grabbing the keys array inside, but I don't know how to find the id of the object in the array, which (object) I need to edit.
There is a lot of nesting and this might cause some issues.
EDIT 2: I'm attacking my account model. Forgot about it earlier. Sorry.
var mongoose = require('mongoose');
var website = require('./website');
var plm = require('passport-local-mongoose');
var accountSchema = new mongoose.Schema({
isPremium: Boolean,
accType: String,
websites: [],
keys: [
{ name: String, value: String },
{ name: String, value: String },
{ name: String, value: String },
{ name: String, value: String },
{ name: String, value: String },
{ name: String, value: String },
{ name: String, value: String },
],
});
accountSchema.plugin(plm);
module.exports = mongoose.model('Account', accountSchema);
You can perform the update atomically using $positional operator.
You include the field (_id) from the keys to locate the index of element and replace the placeholder($) with the found index from query part in the update part to set the value in keys.
router.post('/keys/edit/:_id', isAdmin, function(req, res, next) {
var p = req.params;
var b = req.body;
Account.findOneAndUpdate(
{_id: req.user._id,'keys._id':req.params._id },
{$set:{'keys.$.value':req.body.keyValue}},
{new: true},
function(err, account) {}
);
The question isn't entirely clear to me what you're looking to do, but what I can infer is that you want to do the following:
You have some object that has an Array of keys that has the following shape:
{
"_id" : ObjectId("5a8d83d5d5048f1c9ae877af"),
"name" : "Google",
"value" : ""
}
Judging from your sample object, I'm inferring the schema is defined something like:
const mongoose = require('mongoose')
const definition = {
websites: [String],
keys: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Key'
}]
}
const accountSchema = new mongoose.Schema(definition)
module.exports = mongoose.model('Account', topicSchema)
By the looks of the route, you want to update/edit that object at the given index: keys[i]. If this is the case, then there is no need to manually traverse the array, update the model directly:
const Key = require('./path/to/models/Key')
router.post('/keys/edit/:id', async (req, res) => {
const { keyValue } = req.body
const conditions = { _id: req.params.id }
await Key.findOneAndUpdate({ id }, { value: keyValue }).exec()
res.status(201).json()
})
The item in the array will be updated when you query the parent model.

Finding users who has signed between given dates using Mongoose

I have user model like this
const guestSchema = mongoose.Schema({
facebook: {
id: String,
token: String,
email: String,
name: String,
phone: String,
dates: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "loginDate"
}
]
}
});
and loginDate model
const loginDateSchema = mongoose.Schema({
loginDate: Date
});
Every time user signs in current Date is added.
How can I find users who have signed in between given date?
I did below but I am getting empty result
Guest.find({ "facebook.id": { $exists: true } })
.populate("facebook.dates")
.find({ "facebook.dates": { $gte: startDate,$lte: endDate } })
.exec((err, foundUsers) => {
res.render("./admin/send", {
facebookUsers: foundUsers
});
});
Sample JSON
{
"_id" : ObjectId("5a1a838f58eb1a50c408de84"),
"facebook" : {
"email" : "sample#yahoo.com",
"name" : "Sample user",
"id" : "12345",
"dates" : [
ObjectId("5a1a838f58eb1a50c408de85"),
ObjectId("5a1a839258eb1a50c408de86"),
ObjectId("5a1a839358eb1a50c408de87"),
ObjectId("5a1a839758eb1a50c408de88"),
ObjectId("5a1aa17058eb1a50c408de8b")
]
},
"__v" : NumberInt(5)
}
LoginDate
{
"_id" : ObjectId("5a1a839358eb1a50c408de87"),
"loginDate" : ISODate("2017-11-26T09:04:19.107+0000"),
"__v" : NumberInt(0)
}
I think the issue is with your date which you are passing into find function on line
.find({ "facebook.dates": { $gte: startDate,$lte: endDate } })
You need to pass a date object and not a date string.
Here is my solution which is working fine for me
Facebook model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.Promise = global.Promise;
var facebookSchema = new Schema({
id: String,
token: String,
email: String,
name: String,
phone: String,
logindate: Date
},
{
versionKey: false
}
);
module.exports = mongoose.model('Facebook', facebookSchema);
Here is data
[
{
"_id":"5a2949d40591a2192c8fad6b",
"logindate":"2016-10-10T06:28:37.146Z",
"phone":"03006351611",
"name":"mainuhassan",
"email":"mainuhassan#gmail.com",
"token":"MxuXmblL56aqt17aHh1rqcyeHc0E4CwQ",
"id":"100"
},
{
"_id":"5a294cd26c3e661514f3699e",
"logindate":"2015-10-10T06:28:37.146Z",
"phone":"03006351611",
"name":"mainuhassan1",
"email":"mainuhassan1#gmail.com",
"token":"MxuXmblL56aqt17aHh1rqcyeHc0E4CwQ",
"id":"100"
},
{
"_id":"5a294cfe75e2ba2778e9f249",
"logindate":"2017-10-10T06:28:37.146Z",
"phone":"03006351612",
"name":"mainuhassan2",
"email":"mainuhassan2#gmail.com",
"token":"MxuXmblL56aqt17aHh1rqcyeHc0E4CwQ",
"id":"100"
}
]
And here is my code for getting data between dates
Facebook.find({ logindate: { "$gte": new Date("2016-10-10T06:28:37.146Z"), "$lte": new Date("2017-10-10T06:28:37.146Z") } })
.exec(function(error, facebook){
if (!error) {
res.send(facebook);
}
});

Update a specific field in a croncrete JSON object inside an JSON array (Mongoose)

I am trying to update a specific field in a concrete JSON object inside an JSON array with mongoose.
My MongoDB contains:
db.users.find({username:"xxx"})
{ "_id" : ObjectId("56cb877e73da764818ec5ded"),..., "githubtoken" : [ { "token" : "9e37axxx", "username" : "xxx", "_id" : ObjectId("572a7cfafe95dec51d9cbf2d") }, { "token" : "4529yyy", "username" : "yyy", "_id" : ObjectId("572a7d3cfe95dec51d9cbf2e") } ] }
And I want to get the JSON object that matches with "username" : "yyy"and "user._id" = "56cb877e73da764818ec5ded" and change its token to "token" : "4529zzz" with mongoose, like this:
{ "_id" : ObjectId("56cb877e73da764818ec5ded"),..., "githubtoken" : [ { "token" : "9e37axxx", "username" : "xxx", "_id" : ObjectId("572a7cfafe95dec51d9cbf2d") }, { "token" : "4529zzz", "username" : "yyy", "_id" : ObjectId("572a7d3cfe95dec51d9cbf2e") } ] }
The schema of db is:
var userSchema = new Schema({
username : { type: String, required: true },
...
githubtoken: [ { username: {type: String, required: false },
token: {type: String, required: false} }]
});
And update method:
userSchema.statics.updateuser = function updateuser (query, update, options) {
var promise = new Hope.Promise();
this.findAndUpdate(query, update, options,function(error, user) {
if (error) {
return promise.done(error, null);
}else {
return promise.done(null, user);
}
});
return promise;
};
And in my service with express.js:
query = {$and: [{_id: userid}, {githubtoken: {username:username, token:oldToken}} ]};
update = {$set : {githubtoken:{username:username, token:token}}};
options = { new: true};
User.updateuser(query,update,options).then(function (error,user){
if(error){
return promise.done(error,null);
}else{
}});
But it doesn't work, because remove all array of githubtokens and push only the new githubtoken, like this:
{ "_id" : ObjectId("56cb877e73da764818ec5ded"),..., "githubtoken" : { "token" : "4529zzz", "username" : "yyy", "_id" : ObjectId("572a7d3cfe95dec51d9cbf2e") } }
Any idea?
Thank you very much :D
You can use the $ - positional operator for the update:
db.collection('users').update({
"_id": ObjectId("56cb877e73da764818ec5ded"),
"githubtoken.username": "xxx"
}, {
$set: {
"githubtoken.$.username": username,
"githubtoken.$.token": token
}
});
And this should do the trick. More details on usage of $ - positional operator https://docs.mongodb.org/manual/reference/operator/update/positional/
Try changing the query like this:
var query = {
{
_id: new ObjectId(userid),
'githubtoken.username': username,
'githubtoken.token': oldToken
}
};
And update like this:
var update = {
$set: {
'githubtoken.$.username': username,
'githubtoken.$.token': token
}
};
I found the solution in this page:
query = {"githubtoken" :{$elemMatch: {"username": username}}};
update = {$set : {
"githubtoken.$.username": username,
"githubtoken.$.token": token
}};
Thanks for the help :D

MongoDB ObjectID in JS

I am trying to make pagination work with MongoDB without skip();
In mongo shell I got the following results with my query, but in Javascript a empty [];
I think I am doing the ObjectID wrong, I use the "mongodb ObjectID" and "mongojs" libs with Node.js.
Mongo shell:
db.chat.find({
_id: { $lt: ObjectId("53e901c125c68270311e5f41") },
user_id: 1,
target_user_id: 1,
"$or": [{user_id: 1, target_user_id:1}]
}).sort({
ts: -1
}).limit(5);
Output:
{ "_id" : ObjectId("53e88e1bb76e781413000029"), "user_id" : 1, "target_user_id" : 1, "message" : "Hey" }
{ "_id" : ObjectId("53e88f51b76e78141300002a"), "user_id" : 1, "target_user_id" : 1, "message" : "Hey" }
//ect.
Javascript
var ObjectID = require('mongodb').ObjectID;
var db = require("mongojs").connect(db_uri, db_collections);
//last_ts = "53e901c125c68270311e5f41"
var last_id = ObjectID.createFromHexString(last_ts);
db.chat.find({
_id: { $lt: last_id },
user_id: 1,
target_user_id: 1,
"$or": [{user_id: 1, target_user_id:1}]
}).sort({
ts: -1
}).limit(5).toArray(function (err, docs) {
console.log("docs:"+docs); //"docs" - no result
console.log("err:"+err); //"err:null"
if(docs != null){
console.log(JSON.stringify(docs)); //"docs:[]"
}
});
How can I get the same result, with my query in JS?
Edit with $oid from the docs:
http://docs.mongodb.org/manual/reference/mongodb-extended-json/#oid
Still not working..
var last_ts = "53e901c125c68270311e5f41";
db.chat.find({_id: {$lt: {"$oid": last_ts}}, user_id:1, target_user_id:1, "$or": [{user_id: 1, target_user_id:1}]}).sort({ts: -1}).limit(5)
Edit
Now working with simply as:
var last_ts = "53e901c125c68270311e5f41";
new ObjectID(last_ts)
If I'm not mistaken, you can simply pass the ObjectID string to the mongo query:
db.chat.find({
_id: { $lt: last_ts},
user_id: 1,
target_user_id: 1,
"$or": [{user_id: 1, target_user_id:1}]
})
...

Categories

Resources