Push an object into a nested array in MongoDB - mongodb

I've got a head-scratcher here that I'd like to share with you all.
So here's the model:
_id: ObjectId()
name: String,
columns: [
{
name: String,
_id: ObjectId()
tasks: [
{
title: String,
description: String,
status: String,
_id: ObjectId()
subtasks: [
{
title: String,
isCompleted: Boolean,
},
],
},
],
},
],
});
and the query:
exports.createSubtask = (req, res) => {
if (!req.body) {
res.status(400).send({ message: "Task name can not be empty!" });
return;
}
const board = req.params.board;
const column = req.params.column;
const task = req.params.task;
Board.findOneAndUpdate(
{
_id: board,
"columns._id": column,
"columns.tasks._id": task,
},
{
$push: {
"columns.$.tasks.$.subtasks": req.body,
},
}
)
.then((data) => {
if (!data) {
res.status(404).send({
message: `Cannot update Task with id=${task}. Maybe task was not found!`,
});
} else res.send({ message: "Task was updated successfully." });
})
.catch((err) => {
res.status(500).send({
message: "Error updating Task with id=" + task,
});
});
};
I'm trying to push an object into the subtasks array with $push, but Postman is throwing an error.
Any ideas as to what I'm doing wrong? Appreciate the help.
Golden Ratio
However, I was able to successfully push an object into the tasks array with the following query:
exports.createTask = (req, res) => {
if (!req.body) {
res.status(400).send({ message: "Task name can not be empty!" });
return;
}
const board = req.params.board;
const column = req.params.column;
Board.findOneAndUpdate(
{
_id: board,
"columns._id": column,
},
{
$push: {
"columns.$.tasks": req.body,
},
}
)
.then((data) => {
if (!data) {
res.status(404).send({
message: `Cannot update Column with id=${column}. Maybe column was not found!`,
});
} else res.send({ message: "Column was updated successfully." });
})
.catch((err) => {
res.status(500).send({
message: "Error updating Column with id=" + column,
});
});
};

It is not possible to use multiple positional $ for the nested array as mention in docs:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
You should work with the positional filtered operator $[<identifier>].
Board.findOneAndUpdate(
{
_id: board,
"columns._id": column,
"columns.tasks._id": task,
},
{
$push: {
"columns.$.tasks.$[task].subtasks": req.body,
},
},
{
arrayFilters: [
{ "task._id": task }
]
}
)
.then(...);
Note: Ensure that the passed in task is ObjectId type.

Credit to Yong Shun Yong for the help. Through trial and error, I solved the problem with the following code
Board.findOneAndUpdate(
{
_id: board,
"columns._id": column,
},
{
$push: {
"columns.$.tasks.$[].subtasks": req.body,
},
},
{
arrayFilters: [{ "task._id": task }],
}
)

Related

Editing specific element of an array of a particular collection - mongoDb

I am new to mongoDb. I have created a collection named task that has comments field which is array along with other fields. I need to edit specific comment of the task. There is a edit button in each comment. Both task id and comment id are available. Now how to edit specific comment of the task?
Thanks in advance
task api
{
"status":true,
"task":[
{
"_id":"61dfef323a6ee474c4eba926",
"description":"hello there",
"title":"hello",
"comments":[
{
"comment_id":1,
"username":"test",
"comment":"abcd",
"status":true,
},
{
"comment_id":2,
"username":"test",
"comment":"abcdsdfsdf",
"status":true,
}
],
"createdAt":"2022-01-13T09:21:54.795Z",
"updatedAt":"2022-01-13T09:21:54.795Z",
"__v":0
}
]
}
Task model schema
const taskSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
comments: [Object],
}, {
timestamps: true,
});
I tried using $set but I don't know how to use it in the inner array.
router.route('./comments/edit').post((req, res) => {
const commentId = req.body.commentId;
const taskId = req.body.postId;
const comment = req.body.editedComment;
const updatedAt = new Date();
Task.updateOne(
{ _id: taskId},
{
//what to do here?
// $set: { comments: [ {'comment_id': commentId} ]},
}
)
.then((response) => res.json({ status: true, msg: 'Comment Edited!' }))
.catch(err => res.json({ status: false, msg: err }));
});
Thanks in advance.
This is how to do best:
db.collection.update({
status: true
},
{
$set: {
"task.$[x].comments.$[y].username": "New Name"
}
},
{
arrayFilters: [
{
"x._id": "61dfef323a6ee474c4eba926"
},
{
"y.comment_id": 2
}
]
})
Explained:
Define x and y as arrayFIlters in the update statement.
In the $set statement provide the x & y filters to identify the specific comment for update.
In the example I update the username , but you can update any other value from the targeted array subelement addressed by x & y.
playground
And here is how to update two values at same time in the same nested array element.

Mongoose update only fields available in request body

I am trying to update one document using findOneAndUpdate and $set but I clearly missing something very crucial here because the new request is overwriting old values.
My Device schema looks like this:
{
deviceId: {
type: String,
immutable: true,
required: true,
},
version: {
type: String,
required: true,
},
deviceStatus: {
sensors: [
{
sensorId: {
type: String,
enum: ['value1', 'value2', 'value3'],
},
status: { type: Number, min: -1, max: 2 },
},
],
},
}
And I am trying to update the document using this piece of code:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{ $set: req.body },
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
And when I try to send a request from the postman with the body that contains one or multiple sensors, only the last request is saved in the database.
{
"deviceStatus": {
"sensors": [
{
"sensorId": "test",
"status": 1
}
]
}
}
I would like to be able to update values that are already in the database based on req.body or add new ones if needed. Any help will be appreciated.
The documentation said:
The $set operator replaces the value of a field with the specified
value.
You need the $push operator, it appends a specified value to an array.
Having this documents:
[
{
_id: 1,
"array": [
2,
4,
6
]
},
{
_id: 2,
"array": [
1,
3,
5
]
}
]
Using $set operator:
db.collection.update({
_id: 1
},
{
$set: {
array: 10
}
})
Result:
{
"_id": 1,
"array": 10
}
Using $push operator:
db.collection.update({
_id: 1
},
{
$push: {
array: 10
}
})
Result:
{
"_id": 1,
"array": [
2,
4,
6,
10
]
}
you want to using $push and $set in one findOneAndUpdate, that's impossible, I prefer use findById() and process and save() ,so just try
let result = await Device.findById(deviceId )
//implementation business logic on result
await result.save()
If you want to push new sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{
$push: {
"deviceStatus.sensors": { $each: req.body.sensors }
}
},
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
Update to the old answer:
If you want to update sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ "deviceId": deviceId },
{ "deviceStatus": req.body.sensors },
{ upsert: true },
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);

Mongoose - Update/Find Specific Object in an Array Not Working As Expected

I am following the docs without luck and am at a standstill while trying to update an object in an object in an array using MongoDB and Mongoose.
Here is my document:
{
fields: [
{ id: 603d63086db2db00ab09f50f, data: [Object] },
{ id: 603d63086db2db00ab09f510, data: [Object] },
{ id: 603d63086db2db00ab09f511, data: [Object] },
{ id: 603d63086db2db00ab09f512, data: [Object] },
{ id: 603d63086db2db00ab09f513, data: [Object] },
{ id: 603d63086db2db00ab09f514, data: [Object] },
{ id: 603d63086db2db00ab09f515, data: [Object] }
],
layouts: [],
_id: 603d631a6db2db00ab09f517,
bandId: '603d63146db2db00ab09f516',
eventType: 'private',
ownerId: '6039354906410800c14934c1',
__v: 0
}
I am trying to updateOne of the fields.data in the fields array. fields.data is an object as well.
I call my Express/Node Backend to this route.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.updateOne(
{ _id: req.params.id, "fields.id": req.body.id },
{
"$set": {
"fields.$.data": req.body.data,
},
}
);
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
Where req.body is:
{ id: '603d63086db2db00ab09f50f', data: { type: 1, rate: '200.30' } }
**Just in case it's helpful, here is what one of the fields objects looks like in the document,
{"id":"603d63086db2db00ab09f50f","data":{"type":1,"rate":300}}
I have even tried changing my route to find this document - which I have confirmed exists - Truly at a loss why it won't find the document.
Here is how I changed the above route to find the document.
//Update
router.put("/:id", async (req, res) => {
try {
let updating = await QuoteGenerator.find(
{ _id: req.params.id, "fields.id": req.body.id },
);
console.log(updating) //returns []
let item = await QuoteGenerator.findOne({ _id: req.params.id });
res.json({ success: "Item Updated.", item });
} catch (err) {
console.log(err);
res.json({ error: "Something went wrong when updating this item." });
}
});
The Model
//Create Schema - QG
const QuoteGeneratorSchema = new Schema({
bandId: {
type: String,
required: true,
},
ownerId: {
type: String,
required: true,
},
fields: {
type: Array,
default: defaultFields,
required: true,
},
eventType: {
type: String,
required: false,
},
layouts: {
type: Array,
required: false,
},
});
let QuoteGenerator = mongoose.model("QuoteGenerator", QuoteGeneratorSchema);
module.exports = QuoteGenerator;
Any nudge in the right direction to replacing that data object with a new data object would be extremely helpful! Thanks!

MongoDB: aggregate error when use Match and Group

I've model
var LogSchema = mongoose.Schema({
userId: String,
pageId: String,
tagId: String
}, {
timestamps: true
});
In code,
Log.aggregate([
{
$match: {
createdAt: {
$gte: new Date(strFrom),
$lte: new Date(strTo),
}
},
//$group: { _id: "$userId" },
}
], function (err, logs) {
if (err) {
res.status(500).send({ message: "error retrieving logs." });
} else {
res.send(logs);
}
});
When I execute code use $match, that's ok. Then, I add $group, I receive error
Error: Arguments must be aggregate pipeline operators
So, I remove $match, only use $grooup, code run ok. So, when I use both $match and $group, receive errors.
Please, give me ideas
Thank so much
You have a missing brace.
Log.aggregate([{
$match: {
createdAt: {
$gte: new Date(strFrom),
$lte: new Date(strTo),
}
},
{ <-- missing brace around your group
$group: { _id: "$userId" },
}],
function (err, logs) {
if (err) {
res.status(500).send({ message: "error retrieving logs." });
} else {
res.send(logs);
}
});

How to indicate an update with findAndModify method in MongoDB?

I'm working on an app using MongoDB and Express.js.
I am creating a post handler that updates a toy (found by its id) with a new proposed name for the toy (which is pushed onto a nameIds array that contains the ids of the other proposed names):
router.post('/names', (req, res) => {
const toyId = req.body.toyId;
const name = req.body.newName;
mdb.collection('names').insertOne({ name }).then(result =>
mdb.collection('toys').findAndModify({
query: { id: toyId },
update: { $push: { nameIds: result.insertedId } },
new: true
}).then(doc =>
res.send({
updatedToy: doc.value,
newName: { id: result.insertedId, name }
})
)
)
});
However, when I test this, I receive this error:
name: 'MongoError',
message: 'Either an update or remove=true must be specified',
ok: 0,
errmsg: 'Either an update or remove=true must be specified',
code: 9,
codeName: 'FailedToParse'
I'm not new to MongoDB, but this simple call is baffling me.
Thanks for any help you can provide!
That is the format for mongo shell. Using mongo driver you would call with these arguments:
.findAndModify( //query, sort, doc, options, callback
{ id: toyId }, //query
[], //sort
{ $push: { nameIds: result.insertedId } }, // doc update
{ new: true }, // options
function(err,result){ //callback
if (err) {
throw err
} else {
res.send({
updatedToy: result.value,
newName: { id: result.insertedId, name }
})
}
}
)