How to update array of key/value pairs into a mongoose? - javascript

Background: I have an array of objects sent from Vue Axios. These objects are key value pairs which is stored in the req.body.
Req.body
keyValue: [{key:"some key", value:"some value"}, {key:"some key2", value:"some value2"}]
Note: I can expect to receive a req.body with a numerous amount objects withing the array. I can not access the "key" and "value" in the objects without adding a [ ] or req.body.keyValue[0].
How can I dynamically add each object's "key" and "value" into mongoose without having to explicitly call a specific object?
I am trying to do something like this:(failed attempt)
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Schema = new Schema({
Pair: [{
Key: String,
Value: Number
}]
});
router.post("/", (req, res) => {
User.update({},
{$push:
{Pair:{
Key: req.body.keyValue.key,
Value: req.body.keyValue.value
}
}
},
(err,result)=>{
if(err){
console.log(err);
res.status(400).send('Error')
}else{
res.status(200).send(result);
}
})
}
I hope I was able to explain well enough. Let me know if there is any confusions. Thanks!

User Schema
userId: {
type: String
},
password: {
type: String
},
friends: [{
userId: String,
followSent: Boolean,
followAccepted: Boolean,
followbackSent: Boolean,
followbackAccepted: Boolean,
inchats: Boolean
}]
Update Code
userModel.updateOne({ userId: "MyId" , "friends.userId": "frndId"},{
$set: {
'friends.$.followSent': true}},(err, docs) => {
})

The point here is that when your call req.body.keyValue.key and req.body.keyValue.value, they are in a javascript array req.body.keyValue[].
Presuming that the req.body.keyValue will be always a valid array with the { key : '...', value : '...' } you can use the MongoDB $each operator to update your document.
As:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Schema = new Schema({
Pair: [{
key: String,
value: Number
}]
});
router.post("/", (req, res) => {
User.update(
{},
{
$push: {
Pair:{
$each : req.body.keyValue
}
}
},
(err,result)=>{
if(err){
console.log(err);
res.status(400).send('Error')
}else{
res.status(200).send(result);
}
}
);
}
Now just be careful that req.body.keyValue has the right capitalization on each element, so you don't have *K*ey and/or *k*ey, that will not match your schema. =]
Edit
Just to explain how the $each operator will work, see the following example:
req.body = {
keyValue : [
{ key : "key1", value : 1 },
{ key : "key2", value : 2 }
]
};
Document in User collection before the update:
{
_id : ObjectId(),
Pair : [
{ key : "key_A", value : 99 }
]
}
After the .update() with the $each operator, the expected updated document:
{
_id : ObjectId(),
Pair : [
{ key : "key_A", value : 99 },
{ key : "key1", value : 1 },
{ key : "key2", value : 2 }
]
}

Related

Remove all elements in string array Mongoose schema

I'm trying to remove all of strings that match occurrences in the array of 'interestedStudents' in a Mongoose schema.
My Mongoose schema looks like this:
// Create a schema.
const schema = new mongoose.Schema<Post>({
interestedStudents: {
type: [{
type: String,
unique: true
}],
required: false,
},
})
//Create model
export const PostModel = model<Post>('Post', schema);
I'm trying to remove by using:
await PostModel.updateMany({ interestedStudents: { $pullAll : [userId]}})
But I'm getting the following error:
"CastError: Cast to [string] failed for value "[ { '$pullAll': [ '62854109cf9a6db1fcf0393b' ] } ]" (type string) at path "interestedStudents.0" because of "CastError"\n at model.Query.exec
What am I doing wrong? Is my Schema set up wrong? Maybe it's not an array of string?
It was as easy as this for anyone else coming here:
const { modifiedCount } = await PostModel.updateMany({}, { $pull: { interestedStudents: userId } })

change values of nested objects by id with mongoose

I'm new to MongoDB, and I'm trying to do a very simple task, but however I can't get it right.
What I want is to change the process status but I tried "FindAndUpdate", "UpdateOne" and "FindByIdAndUpdate" but it won't work.
Maybe it has to do with my Schema. Should I create a new Schema for the Process?
My Database entry inside a MongoDB Collection:
_id: 622c98cfc872bcb2578b97a5
username:"foo"
__v:0
process:Array
0: Object
processname:"bar"
process_status:"stopped"
_id: 6230c1a401c66fc025d3cb88
My current Schema:
const User = new mongoose.Schema(
{
username: { type: String, required: true },
process: [
{
processname: {
type: String,
},
process_status: {
type: String,
},
},
],
},
{ collection: "user-data" }
);
My current code:
const startstopprocess = await User.findByIdAndUpdate(
{ _id: "6230c1a401c66fc025d3cb88" },
{ process_status: "started" }
).then(function (error, result) {
console.log(error);
console.log(result);
});
You can use positional operator $ in this way:
db.collection.update({
"process._id": "6230c1a401c66fc025d3cb88"
},
{
"$set": {
"process.$.process_status": "started"
}
})
Note how using positional operator you can say mongo "from the object you have found in find stage, update the process_status variable to started"
Example here

MongoDB Aggregate is not matching specific field

I'm new to Aggregation in MongoDB and I'm trying to understand the concepts of it by making examples.
I'm trying to paginate my subdocuments using aggregation but the returned document is always the overall values of all document's specific field.
I want to paginate my following field which contains an array of Object IDs.
I have this User Schema:
const UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
firstname: String,
lastname: String,
following: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
...
}, { timestamps: true, toJSON: { virtuals: true }, toObject: { getters: true, virtuals: true } });
Without aggregation, I am able to paginate following,
I have this route which gets the user's post by their username
router.get(
'/v1/:username/following',
isAuthenticated,
async (req, res, next) => {
try {
const { username } = req.params;
const { offset: off } = req.query;
let offset = 0;
if (typeof off !== undefined && !isNaN(off)) offset = parseInt(off);
const limit = 2;
const skip = offset * limit;
const user = await User
.findOne({ username })
.populate({
path: 'following',
select: 'profilePicture username fullname',
options: {
skip,
limit,
}
})
res.status(200).send(user.following);
} catch (e) {
console.log(e);
res.status(500).send(e)
}
}
);
And my pagination version using aggregate:
const following = await User.aggregate([
{
$match: { username }
},
{
$lookup: {
'from': User.collection.name,
'let': { 'following': '$following' },
'pipeline': [
{
$project: {
'fullname': 1,
'username': 1,
'profilePicture': 1
}
}
],
'as': 'following'
},
}, {
$project: {
'_id': 0,
'following': {
$slice: ['$following', skip, limit]
}
}
}
]);
Suppose I have this documents:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'gagi',
following: []
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'kuku',
following: []
},
{
_id: '76jghkdfhasjhfsdkf',
username: 'john',
following: ['5fdgffdgfdgdsfsdfsf', '5fgjhkljvlkdsjfsldkf']
},
]
And when I test my route for user john: /john/following, everything is fine but when I test for different user which doesn't have any following: /gagi/following, the returned result is the same as john's following which aggregate doesn't seem to match user by username.
/john/following | following: 2
/kuku/following | following: 0
Aggregate result:
[
{
_id: '5fdgffdgfdgdsfsdfsf',
username: 'kuku',
...
},
{
_id: '5fgjhkljvlkdsjfsldkf',
username: 'gagi',
...
}
]
I expect /kuku/following to return an empty array [] but the result is same as john's. Actually, all username I test return the same result.
I'm thinking that there must be wrong with my implementation since I've only started exploring aggregation.
Mongoose uses a DBRef to be able to populate the field after it has been retrieved.
DBRefs are only handled on the client side, MongoDB aggregation does not have any operators for handling those.
The reason that aggregation pipeline is returning all of the users is the lookup's pipeline does not have a match stage, so all of the documents in the collection are selected and included in the lookup.
The sample document there is showing an array of strings instead of DBRefs, which wouldn't work with populate.
Essentially, you must decide whether you want to use aggregation or populate to handle the join.
For populate, use the ref as shown in that sample schema.
For aggregate, store an array of ObjectId so you can use lookup to link with the _id field.

MongoDB / NodeJS can't push to array

I am trying to add tags to existing tags in a MongoDB collection with this Schema:
const workSchema = new mongoose.Schema({
title: {
type: String,
required: "Tile can't be blank"
},
description: {
type: String
},
imageURL: {
type: String,
unique: true
},
workURL:{
type: String,
unique: true
},
tags:{
type:Array
},
createdDate: {
type: Date,
default: Date.now
}
});
const Work = mongoose.model('Work', workSchema);
module.exports = Work;
I made an API that makes a PUT request to "/api/work/:workId/tags"
exports.updateTags = (req, res) =>{
try{
const newTags = req.body.tags.split(',');
newTags.forEach(tag => {
db.Work.update(
{"_id": req.params.workId},
{
$push:{
tags: tag
}
}
)
})
res.status(200).send({message : "tags updated"})
}
catch(error){
res.status(400).send(error)
}
}
request.body:
{
tags:"a,b,c"
}
The problem is that the array won't update with the new tag values
I searched for other ways to update in the docs and on the web but I didn't find any solutions.
You haven't defined _id in your workSchema so the type of _id would be ObjectId
But req.params.workId is probably a String, so querying an ObjectId with a String won't work.
So you should convert req.params.workId to ObjectId using mongoose.Types.ObjectId
{ "_id": mongoose.Types.ObjectId(req.params.workId) }
But you can improve your code a bit more by using .findByIdAndUpdate and $each operator
.findByIdAndUpdate will automatically convert your _id to ObjectId
You can use $each to $push multiple array elements at the same time without using .forEach
Work.findByIdAndUpdate(req.params.workId, {
$push: { "tags": { $each: newTags } }
})

Set field in mongoose document to array length

I have a Mongoose document (Mongoose 5.4.13, mongoDB 4.0.12):
var SkillSchema = new mongoose.Schema({
skill: { type: String },
count: { type: Number, default: 0 },
associatedUsers: [{ type : mongoose.Schema.Types.ObjectId, ref: 'User' }]
});
That I update as follows:
var query = { skill: req.body.skill };
var update = { $addToSet: { associatedUsers: req.params.id } };
var options = { upsert: true, new: true, setDefaultsOnInsert: true };
await skillSchema.findOneAndUpdate(query, update, options);
During this update, I would like to also update count to be equal to the length of associatedUsers.
Ideally I want this to happen at the same time as updating the other fields (i.e not in a subsequent update), either via a pre-hook or within findOneAndUpdate.
I've tried using a pre hook after schema definition:
SkillSchema.pre('findOneAndUpdate', async function(){
console.log("counting associated users");
this.count = this.associatedUsers.length;
next();
});
As well as using aggregate in my UPDATE route:
await skillSchema.aggregate([{ $project: { count: { $size: "$associatedUsers" } } } ])
But I can't get either to work.
Does anyone have any suggestions for how I could achieve this?
You could use $set like this in 4.2 which supports aggregation pipeline in update.
The first $set stage calculates a associatedUsers based on the previous and new value. $setUnion to keep the distinct associatedUsers values.
The second $set stage calculates tally based on the associatedUsers calculated in the previous stage.$size to calculate the length of associatedUsers values.
var query = {skill: req.body.skill};
var update = [{ $set: { "associatedUsers":{"$setUnion":[{"$ifNull":["$associatedUsers",[]]}, [req.params.id]] }}}, {$set:{tally:{ $size: "$associatedUsers" }}}];
var options = { upsert: true, new: true, setDefaultsOnInsert: true };
await skillSchema.findOneAndUpdate(query, update, options)
If any argument resolves to a value of null or refers to a field that is missing, $setUnion returns null. So just needed to safeguard our operation with $ifNull
About tally and associatedUsers.length
// define your schema object
var schemaObj = {
skill: { type: String },
associatedUsers: { type: Array }
};
// get the length of users
var lengthOfAsUsers = schemaObj.associatedUsers.length;
// add tally to schema object and set default to the length of users
schemaObj.tally = { type: Number, default: lengthOfAsUsers };
// and pass your schema object to mongoose.Schema
var SkillSchema = new mongoose.Schema(schemaObj);
module.exports = SkillSchema;
EDIT
you can update tally subsequently, but recommended solution would be to use this method
https://mongoosejs.com/docs/populate.html
const id = "nameSomeId";
SkillSchema.find({ _id: id }).then(resp => {
const tallyToUpdate = resp.associatedUsers.length;
SkillSchema.findOneAndUpdate({ _id: id }, { tally: tallyToUpdate }).then(
resp => {
console.log(resp);
}
);
});
The solution I have will only work on mongodb v 4.2 as it has option to use aggregate in the update and will only need one query as:
skillSchemafindOneAndUpdate(
{skill:"art"},
[
{ $set: {
associatedUsers:{
$cond:{
if: {$gte: [{$indexOfArray: ["$associatedUsers", mongoose.Types.ObjectId(req.params.id)]}, 0]},
then: "$associatedUsers",
else: { $cond:{
if: { $isArray: "$associatedUsers" },
then: {$concatArrays:["$associatedUsers",[mongoose.Types.ObjectId(req.params.id)]]},
else: [mongoose.Types.ObjectId(req.params.id)]
}}
}
}}},
{$set:{
associatedUsers:"$associatedUsers",
tally:{$size:"$associatedUsers"},
}}
],
{upsert:true,new:true}
)
ref: https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline
The "Group" field does not appear in the schema. On MongoDB Shell, these codes will work.
However, Mongoose will also give an error because the schema is validated.
Is the "Group" field a dynamic field? I think the problem with the schema will be solved.
var mongoose = require("mongoose");
var SkillSchema = new mongoose.Schema({
skill: { type: String },
tally: { type: Number, default: 0 },
associatedUsers: { type: Array },
group: { type: Array }
});

Categories

Resources