MongoDB push to array with predefined index - mongodb

How do I add an item to Mongoose, if I want to push it to an item of the array?
I want to push it to the document with predefined _id, to the 'productList' array with predefined 'id', to the 'items' array.
{
"_id" : ObjectId("5ba94316a48a4c828788bcc9"),
"productList" : [
{
"id" : 1,
"items" : [
{
"id" : 1,
"name" : "FLOSS 500",
}
]
}
]
}
I thought that it should be something like this, but it did not work:
Products.findOneAndUpdate({_id: req.body._id, productList: {id: req.body.id}}, {$push: {'items': req.body.product}})

You can try this with positional operator $. For search by nested array property use dot-separated syntax:
Products.findOneAndUpdate({
_id: req.body._id,
'productList.id': req.body.id
}, { $push: { 'productList.$.items': req.body.product } });
Full example:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const Products = mongoose.model('Test', new Schema({
productList: []
}));
mongoose.connect("mongodb://localhost:27017/myapp");
let item = new Products({
"_id": mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
"productList": [
{
"id": 1,
"items": [
{
"id": 1,
"name": "FLOSS 500",
}
]
}
]
});
Products.deleteMany({}).then(() => {
return Products.create(item);
}).then(() => {
return Products.findOneAndUpdate({
_id: mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
'productList.id': 1
}, {
$push: {
'productList.$.items': {
"id": 2,
"name": "FLOSS 600",
}
}
});
}).then(() => {
return Products.find({
_id: mongoose.Types.ObjectId("5ba94316a48a4c828788bcc9"),
'productList.id': 1
});
}).then(data => {
console.log(data);
if (data) {
console.log(data[0].productList);
/* [{"id":1,"items":[{"id":1,"name":"FLOSS 500"},{"id":2,"name":"FLOSS 600"}]}] */
}
}).catch(err => {
console.error(err);
});

Related

Upsert in nested array doesn't create parent document

Schema
{
chapter: {
required: true,
type: Schema.Types.ObjectId,
ref: "Chapter",
},
questions: {
type: [Number]
},
};
Here is an example document
{
"_id":{
"$oid":"5ff4b728b6af610f0851d2a6"
},
"chapters":[
{
"chapter":{
"$oid":"611478ab34dde61f28dbe4d3"
},
"questions":[
35,
29,
167,
180,
101,
16,
71,
23
]
},
{
"chapter":{
"$oid":"611478ac34dde61f28dbe4d8"
},
"questions":[
162
]
}
]
}
I want to "$addToSet" on "questions", such as
const someId = SOME_ID;
const chapterId = "611478ac34dde61f28dbe4d8";
const update = {
$addToSet: {
"chapters.$.questions": {
$each: [5, 10, 32, 6],
},
},
};
await model.findOneAndUpdate(
{
_id: someId,
"chapters.chapter": chapterId,
},
update,
{ upsert: true }
)
.lean()
.exec();
This works. However, if there is no document, the "upsert" doesn't create the document.
How can I rewrite the operation so that it can update (addToSet) as well as ensure the document is created if it didn't exist?
I checked MongoDB native query use these
db.con.collection('example').updateOne(
{"chapters": {$elemMatch:{"chapter.id":ObjectId("611478ac34dde61f28dbe4d8")}}},
{$addToSet: {
"chapters.$.questions": {
$each: [5, 10, 32, 6],
},
}},
{upsert: true})
you should find the element of array using elemMatch
{"chapters": {$elemMatch:{"chapter.id":"611478ac34dde61f28dbe4d8"}}}
I figured out, for some reason, I can't $addToSet if the parent object is not present. So I had to make one more operation.
Inspired from this Stackoverflow answer.
I fetch the "chapters" which I need to add.
From this list of fetched chapters, I check which ones exist and which ones don't.
Using the knowledge from point 2, I am using $push to add the chapters which didn't exist entirely, and "adding to set ($addToSet)" questions on the chapters which do exist.
I am posting the code which works for me.
//Data to add (which chapter?: questionNumber[])
const docId = "SOMEID";
const questionsToAdd = {
"611478ab34dde61f28dbe4d3": [1,5,6,10],
"611478ab34dde61f28dbe4d8": [5,8,20,30],
};
//Find the chapters from questionsToAdd which exist
const existingDoc = await model.findOne({
_id: docId,
chapters: { $elemMatch: { chapter: { $in: Object.keys(questionsToAdd) } } },
})
.select(["chapters.chapter"])
.lean()
.exec();
// Objectify the array of chapters
const existingChapters = (existingDoc?.chapters ?? []).map((x) => "" + x.chapter);
// Prepare what to insert, what to update
const updateObject = {
$addToSet: {},
arrayFilters: [],
$push: [],
};
for (const [index, [chapterId, questionIndices]] of Object.entries(questionsToAdd).entries()) {
if (existingChapters.includes(chapterId)) {
updateObject.$addToSet["chapters.$[filter" + index + "].questions"] = { $each: questionIndices };
updateObject.arrayFilters.push({
["filter" + index + ".chapter"]: Types.ObjectId(chapterId),
});
} else {
updateObject.$push.push({
chapter: chapterId,
questions: questionIndices,
});
}
}
if (updateObject.arrayFilters.length) {
// *Add to set the chapters which exist
await model.findOneAndUpdate(
{ _id: userId },
{
$addToSet: updateObject.$addToSet,
},
{
arrayFilters: updateObject.arrayFilters,
upsert: true,
}
)
.lean()
.exec();
}
if (updateObject.$push.length) {
// *Push the ones that does not exist
await model.findOneAndUpdate(
{ _id: userId },
{
$push: { chapters: updateObject.$push },
},
{
upsert: true,
}
)
.lean()
.exec();
}

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

MongoDb Titlecase in Collection

In my collection i need to change the firstname and lastname to be in Titlecase.since its in nested array i couldn't proceed.
db.users.find()
{
"users" : {
"assigned" :[
{
"firstName" : "naveen",
"lastName" : "bala",
},
{
"firstName" : "SHAJU",
"lastName" : "HARI",
},
{
"firstName" : "PADMANESH",
"lastName" : "NC",
}
]
}
}
I need the result to be like
{
"firstName" : "Padmanesh",
"lastName" : "Nc",
}
Tried this code below
function titleCase(str) {
return str && str.toLowerCase().split(/\s/).map(function(word) {
return word && word.replace(word[0], word[0].toUpperCase());
}).join(' ');
}
db.users.find().forEach(function(doc){
db.users.updateOne(
{ "_id": doc._id },
{ "$set": { "firstName": titleCase(doc.firstName) } }
);
});
The most efficient way is to use updateMany(). You can see how the titleCase operators work here: https://mongoplayground.net/p/xdePfeBvIQ1
https://docs.mongodb.com/master/reference/method/db.collection.updateMany/index.html
This should do it for you, you can match using the first arg if needed.
Please double check the user schema is correct in your question. If its not this will need to be tweaked. It expects each user doc contains a users object with an assigned property.
db.users.updateMany({}, [{
$set: {
"users.assigned": {
$map: {
input: "$users.assigned",
in: {
firstName: {
$concat:[
{$toUpper: {$substrCP: ["$$this.firstName", 0, 1]}},
{$toLower: {$substrCP: ["$$this.firstName", 1, {$strLenCP: "$$this.firstName"}]}},
]
},
lastName: {
$concat:[
{$toUpper: {$substrCP: ["$$this.lastName", 0, 1]}},
{$toLower: {$substrCP: ["$$this.lastName", 1, {$strLenCP: "$$this.lastName"}]}},
]
}
}
}
}
}
}])
An alternative, to do it on the mongo shell :
var titleCase = function (str) {
return (
str &&
str
.toLowerCase()
.split(/\s/)
.map(function (word) {
return word && word.replace(word[0], word[0].toUpperCase());
})
.join(" ")
);
};
db.users.find().forEach(function (doc) {
var a = doc.users.assigned;
a.forEach(function (person, index) {
var setop = `users.assigned.` + index + `.firstName`;
var uppered = titleCase(person.firstName);
db.users.updateOne(
{ _id: doc._id, "users.assigned.firstName": person.firstName },
{ $set: { [setop]: uppered } }
);
});
});

Can Update document with mongodb query but not work when do in mongoose [duplicate]

This question already has answers here:
Update nested subdocuments in MongoDB with arrayFilters
(2 answers)
Closed 3 years ago.
My collection is like this: https://mongoplayground.net/p/91InBXrUq7R
With this query I can update replies.likes
db.getCollection("posts").updateOne(
{
"_id": ObjectId("5da832caeb173112348e509b"), //posts._id
"comments.replies._id":ObjectId("5db6a88f7c6cfb0d0c2b689b"),//replies._id
},
{ "$push": { "comments.$[outer].replies.$[inner].likes": "10000012" } },
{
"arrayFilters": [
{ "outer._id": ObjectId("5db06e11d0987d0aa2cd5593") },//comments._id
{ "inner._id": ObjectId("5db6a88f7c6cfb0d0c2b689b") }//replies._id
]
}
)
But when I code using mongoose, express, collection not update
//Like Reply toggle
router.post("/toggleLikeReply", function(req, res, next) {
var id_post = req.body.id_post;
var id_comment = req.body.id_comment;
var id_reply = req.body.id_reply;
var id_user = req.user._id;
console.log("id_post: "+id_post+" id_comment: "+id_comment+" id_reply: "+id_reply+" id_user: "+id_user);
//todo
Post.aggregate([
{ $match: {_id: ObjectId(id_post),"comments._id": ObjectId(id_comment)}},
{ $unwind: "$comments"},
{ $match: { "comments._id": ObjectId(id_comment)}},
{ $project: {"replies": "$comments.replies", _id: 0}},
{ $match: { "replies._id": ObjectId(id_reply)}},
{ $project: {"likes": "$replies.likes", _id: 0}},
]).exec((err, users_liked) => {
var index = users_liked[0].likes[0].indexOf(id_user);
console.log(users_liked[0].likes[0]);
//todo
if (index == -1) {
const updatePost = async () => {
try {
await Post.updateOne({
_id: ObjectId(req.body.id_post),
"comments.replies._id": ObjectId(req.body.id_reply)},
{ $push: {"comments.$[outer].replies.$[inner].likes": ObjectId(req.user._id)} },
{
"arrayFilters": [
{ "outer._id": ObjectId(req.body.id_comment) },
{ "inner._id": ObjectId(req.body.id_reply) }
]
}
);
} catch (error) {
console.log("error", error);
}
};
updatePost().then(function(data) {res.send({ like: true, success: true})});
}else{
const updatePost = async () => {
try {
await Post.updateOne({
_id: ObjectId(req.body.id_post),
"comments.replies._id": ObjectId(req.body.id_reply)},
{ $pull: {"comments.$[outer].replies.$[inner].likes": ObjectId(req.user._id)} },
{
"arrayFilters": [
{ "outer._id": ObjectId(req.body.id_comment) },
{ "inner._id": ObjectId(req.body.id_reply) }
]
}
);
} catch (error) {
console.log("💥", error);
}
};
updatePost().then(function(data) {res.send({ like: false, success: true})});
}
})
});
I logged the all the id is come and the same as I did with mongo query directly .
id_post: 5da832caeb173112348e509b
id_comment: 5db06e11d0987d0aa2cd5593
id_reply: 5db6a88f7c6cfb0d0c2b689b
id_user: 5da85558886aee13e4e7f044
What is wrong with my code using mongoose and express?
Try This Query
var mongoose = require('mongoose');
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
const updatePost = async () => {
try {
await Post.updateOne({
_id: ObjectId(req.body.id_post),
"comments.replies._id": ObjectId(req.body.id_reply)},
{ $push: {"comments.$[outer].replies.$[inner].likes": req.user._id} },
{
"arrayFilters": [
{ "outer._id": ObjectId(req.body.id_comment) },
{ "inner._id": ObjectId(req.body.id_reply) }
]
}
);
} catch (error) {
console.log("error", error);
}
};
updatePost().then(function(data) {res.send({ like: true, success: true})});

Mongoose query on subdocument returns array of other subdocument using projection

Update: I am looking for an answer that works within mongodb projection: https://docs.mongodb.com/manual/reference/method/db.collection.findOne/#definition
I am trying to filter a query on a subdocument using projection so that it only returns a specific array. But when filtering the result also includes an array of another subdocument. When I don't filter it only returns the found document.
I tried different filtering options including and excluding positional elements, but can't get the desired return.
Mongoose schema
const stationSchema = new mongoose.Schema({
mac: String,
stationName: String,
syncReadings: Boolean,
temperature: Array,
humidity: Array,
measures: [{
date: Date,
temperature: Number,
humidity: Number
}],
lastUpdated: Date
});
// Define user schema
var userSchema = mongoose.Schema({
local : {
email : String,
password : String
},
facebook : {
id : String,
token : String,
name : String,
email : String
},
twitter : {
id : String,
token : String,
displayName : String,
username : String
},
google : {
id : String,
token : String,
email : String,
name : String
},
apiKey: String,
stations : [stationSchema]
},
{
usePushEach: true
}
);
Api handler
app.get('/api/stations/:stationName/measures', function(req, res, next) {
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
stations.stationName': req.params.stationName
}
const options = {
'stations.measures': 1
}
User.findOne(query, options)
.exec()
.then(stations => {
res.status(200).send(stations)
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
});
There are two stations under one user:
[
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"stationName": "livingroom",
"mac": "5C:CF:7F:77:12:FB",
"_id": "5c39c9ab56bbf002fb092cea",
"lastUpdated": "2019-01-12T11:07:01.802Z",
"syncReadings": false,
"measures": [],
"humidity": [],
"temperature": [
{
"date": "2019-01-12T11:07:01.802Z",
"temperature": "20"
}
]
},
{
"stationName": "office",
"mac": "5C:CF:7F:77:12:FC",
"_id": "5c39cacdce4ac903123f0150",
"measures": [],
"humidity": [],
"temperature": []
}
]
}
]
API call
http://localhost:8080/api/stations/livingroom/measures
Result
{
"_id": "5c39c99356bbf002fb092ce9",
"stations": [
{
"measures": []
},
{
"measures": []
}
]
}
Projection options tried
const options = {
'stations.measures': 1
}
const options = {
'stations.$.measures': 1
}
const options = {
'stations.$': 1,
'stations.$.measures': 1
}
const options = {
'stations.$': 1,
'stations.measures': 1
}
What am I doing wrong?
try using these querying params only and after that you will get a user with a requested station..
var user = {
apiKey: req.user.apiKey
}
const query = {
apiKey: user.apiKey,
'stations.stationName': req.params.stationName
}
then do this
User.findOne(query, options)
.exec()
.then(stations => {
for(let station of stations){
if(station.measures[1]){ // here it is the index
res.status(200).send(stations);
}
}
})
.catch(err => {
console.log(err);
res.status(400).send(err);
})
actually in mongoose you cannot query sub-sub documents to you will have to you this approach.. You can only query sub docs only like you have done