Remove all elements in string array Mongoose schema - javascript

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 } })

Related

Mongoose updateMany documents containing specific Array element

I've got a Mongoose schema set up as follows:
const mongoose = require('mongoose');
const TodoSchema = mongoose.Schema({
id: {
type: String,
required: true,
},
todos: {
type: Array,
required: true,
},
date: {
type: Date,
default: Date.now(),
},
});
module.exports = mongoose.model('todo', TodoSchema, 'todos');
Each of the elements in the todos Array is an Object and has the following format (example):
{
id: 1,
todo: "Do the dishes."
category: "Kitchen"
}
There are multiple documents in my Todo collection and they all contain the same list of Todos. If I wanted to update a specific Todo across ALL documents, I figure I need to use updateMany. I'm using the following in my Todo Update route to update all instances of a Todo:
const { todo } = req.body; // todo.todo contains "Clean the dishes." as an update
Todo.updateMany(
{
todos: { $elemMatch: { id: todo.id } },
},
{ $set: { todo: todo } }
);
I'm assigning the result of the above route code to a variable and console logging the result which comes back with:
{ ok: 0, n: 0, nModified: 0 }
What am I doing wrong? The passed todo id matches the id of a Todo in each of the Todos arrays.
First of all, for your object array, is recommendable create a schema too:
const subSchema = new mongoose.Schema({
id: Number,
todo: String,
category: String
})
const MongooseModel = new mongoose.Schema({
id: String,
todos: [subSchema],
date: Date
})
So now, your array object is defined.
And, the query question is something like that:
db.collection.update({
"todos.id": todo.id
},
{
"$set": {
"todos.$": {newTodo}
}
},
{
"multi": true
})
First, you look for all elements that match your criteria; that is: todos.id = todo.id, then you use $ operator to set all element that match the criteria with your object.
The last line multi is to updated all element that match.
Example playground here
Using moongoose, multi attribute is not neccessary because is set true by default using updateMany().
So moongose query should be something like that.
var update = await model.updateMany(
{
"todos.id": 1
},
{
"$set": {
"todos.$": {
"id": 20,
"todo": "newTodo",
"category": "newCategory"
}
}
})
And for this example data the result is
{ n: 3, nModified: 3, ok: 1 }

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 } }
})

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

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 }
]
}

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 }
});

Why does my GraphQL query to return one record fail, but my query to find all records works fine?

I have a Mongo database with a collection called 'words' which contains documents like this:
{
_id: "xxxx",
word: "AA",
definition: "Cindery lava"
}
I have a node app that I am using to query and display information from the words collection, with GraphQL. I have created a GraphQL schema and Mongoose model, as shown below.
// Schema
const WordType = new GraphQLObjectType({
name: 'Word',
fields: () => ({
id: {type: GraphQLID},
word: { type: GraphQLString },
definition: { type: GraphQLString },
})
})
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
detailsForWord: {
type: WordType,
args: {word: {type: GraphQLString}},
resolve(parent, args) {
return Word.find({word: args.word});
}
},
allWords: {
type: new GraphQLList(WordType),
resolve(parent, args) {
return Word.find({}).limit(100);
}
}
}
});
// model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const wordSchema = new Schema({
word: String,
definition: String,
});
My problem is that the "allWords" query works perfectly but the "detailsForWord" does not work at all, and I have no idea why.
In GraphiQL I am using these queries:
{
allWords {
word
definition
}
}
... and
{
detailsForWord(word: "AA") {
word
definition
}
}
The former returns records, but the latter always returns the following in GraphiQL:
{
"data": {
"detailsForWord": {
"id": null,
"word": null,
"definition": null
}
}
}
Any ideas why the "detailsForWord" query is failing?
Obviously find returns an array of documents while findOne returns a single document. Therefore the query might be successful you are getting an array no matter what with find. findOne returns the document you are looking for. Your query didn't fail, it returned a promise with an array.
if you do
resolve(parent, args) {
return Word.find({word: args.word}).then(c=>{console.log(c);return c})
}
You'll see an array containing the document in the console.

Categories

Resources