MongoDB / NodeJS can't push to array - javascript

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

Related

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

delete mongoDB item using model after a setTime

I am storing chat app messages in MongoDB. After X time i would like them to delete themselves.
Where in the code do i add the line from the Docs
{expireAfterSeconds: x }
My code for creating the item is
try {
MessageModel.create({
username: user.username,
text: msg,
time: moment().format('h:mm a'),
room: user.room
})
} catch (error) {
// do stuff
}
and my model is set out as below
const MessageSchema = new mongoose.Schema(
{
userName: String,
text: String,
time: String,
room: String
},
{ collection: 'messages' }
)
const messageModel = mongoose.model('MessageSchema', MessageSchema)
Do I add the code to the model? or as a second argument to the create method?
Thanks in advance
The MongoDB TTL collection feature is set by using an index.
First, modify your time-field to store a timestamp as a valid date type. You can use moment().toISOString()
const MessageSchema = new mongoose.Schema(
{
userName: String,
text: String,
time: String,
room: String,
},
{ collection: 'messages' }
)
Set the TTL index like so
db.messages.createIndex( { "time": 1 }, { expireAfterSeconds: 3600 } )
For more information look at the docs

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.

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 }

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