Updating multiple sub-documents with Mongoose and Node - node.js

I have a Model wich contains an array of sub-documents. This is a Company:
{
"_id" : ObjectId(":58be7c236dcb5f2feff91ac0"),
"name" : "sky srl",
"contacts" : [
{
"_id" : ObjectId("58be7c236dcb5f2feff91ac2"),
"name": { type: String, required: true },
"company" : ObjectId("58be7c236dcb5f2feff91ac0"),
"email" : "sky#gmail.com",
"chatId" : "",
"phone" : "123456789",
"name" : "John Smith"
},
{
"_id" : ObjectId("58be7f3a6dcb5f2feff91ad3"),
"company" : ObjectId("58be7f3a6dcb5f2feff91ad1"),
"email" : "beta#gmail.com",
"chatId" : "",
"phone" : "987654321",
"name" : "Bill Gaset"
}
],
"__v" : 1
}
I have several companies, and I want to update the field chatId of all the contacts in all the companies, that matches the phone I am searching for.
My Schema definitions (simplified, for focusing on question):
var contactSchema = new Schema({
[...]
phone: { type: String, required: true },
email: { type: String },
chatId: { type: String },
company: Schema.Types.ObjectId,
});
var companySchema = new Schema({
name: { type: String, required: true },
type: { type: String, default: "company" },
contacts: [contactSchema]
});
I tried
var conditions = { "contacts.phone": req.body.phone };
var partialUpdate = req.body; //it contains 'req.body.phone' and 'req.body.chatId' attributes
Company.find(conditions).then(
function (results) {
results.map( function(companyFound) {
companyFound.contacts.forEach(function (contactContainer){
if (contactContainer.phone == partialUpdate.phone) {
contactContainer.chatId = partialUpdate.chatId;
Company.save();
companyFound.save();
contactContainer.save();
results.save();
}
//not sure of what to save, so i save everything
companyFound.save();
contactContainer.save();
results.save();
});
});
});
following this answer; but it doesn't works. It does not save anything, what I'm doing wrong?

I have never done this before, but worth a try.
Maybe you need to use $elemMatch.
// find the companies that have contacts having the phone number
Company.find().where('contacts', { $elemMatch: { phone: req.body.phone }}).exec(function (err, companies) {
if (err) {
console.log(err);
return;
}
// see if you can at least get the query to work
console.log(companies);
async.eachSeries(companies, function updateCompany(company, done) {
// find and update the contacts having the phone number
company.contacts.forEach(function (contact, i, arr) {
if (contact.phone == req.body.phone) {
arr[i].chatId = req.body.chatId;
}
});
company.save(done);
}, function allDone (err) {
console.log(err);
});
});
Note, I am using async.js to do async operations on multiple items.
Honestly, I would have simply made contacts an array of Contact references -- much easier to query and update.

Just for the records: I did this to make it work without async.js:
Company.find().where('contacts', { $elemMatch: { phone: req.body.phone } })
.exec(function (err, companies) {
if (err) {
console.log(err);
return;
}
console.log("companies: " + JSON.stringify(companies, null, 4));
companies.forEach(function (company) {
company.contacts.map(function (contact, i, arr) {
if (contact.phone == req.body.phone) {
arr[i].telegramChatId = req.body.telegramChatId;
}
});
company.save();
},
function allDone(err) {
console.log(err);
});
});`

Related

MongoDB findOne with multiple query returns wrong results

I am trying to query mongodb documents by passing two fields as arguments in a findOne api. Of the two fields passed to the the query statement only can be true at a time. the code is shown below.
//login user with phone or email
userSchema.statics.loginUser = async (userData) => {
const user = await User.findOne({
$or: [{ email: userData.email }, { phone: userData.phone }],
});
if (!user) {
throw new Error("Wrong username or password one");
}
const isMatch = await bcrypt.compare(userData.password, user.password);
if (!isMatch) {
throw new Error("Wrong username or password");
}
return user;
};
But i have noticed that even if i pass a non existent email of phone, the query always returns an existing document. What am i doing wrong in that query?
I think this is happening because userData.email or userData.phone is null and you have a document in your User collection that also has a null email or phone.
When I add the following three records to my database:
> db.User.insertMany([
... {email: "user1#example.com", phone: "555-1234"},
... {email: "user2#example.com", phone: null},
... {email: null, phone: "555-4321"}])
And then perform the following query using the email address of the first user:
> db.User.findOne({$or: [{ email: "user1#example.com" }, { phone: null }]})
I get back the expected record:
{
"_id" : ObjectId("600724ae7d009a501642a783"),
"email" : "user1#example.com",
"phone" : "555-1234"
}
But if I use the this query with an unknown email address:
> db.User.findOne({$or: [{ email: "user3#example.com" }, { phone: null }]})
Then I get the first document with a null phone because there's no match for the email
{
"_id" : ObjectId("600724ae7d009a501642a784"),
"email" : "user2#example.com",
"phone" : null
}
If I change my query to use an $and clause in each $or clause, I now get null when I use a value that doesn't exist and a null phone
> db.User.findOne({$or: [
... { $and: [{ email: { $ne: null } }, { email: "user3#example.com" }] },
... { $and: [{ phone: { $ne: null } }, { phone: null }] }
... ]})
null
And I can still find a record if it exists and is not null, for example this query:
> db.User.findOne({$or: [
... { $and: [{ email: { $ne: null } }, { email: null }] },
... { $and: [{ phone: { $ne: null } }, { phone: "555-1234" }] }
... ]})
Returns this one matching record:
{
"_id" : ObjectId("600724ae7d009a501642a783"),
"email" : "user1#example.com",
"phone" : "555-1234"
}
I also thought of this solution. I transformed the incoming data and removed the undefined data as shown below
let username = {};
username["email"] = userData.email;
username["phone"] = userData.phone;
Object.keys(username).forEach((key) =>
username[key] === undefined ? delete username[key] : {}
);
const user = await User.findOne(username);

Unable to update mongodb document with nested object property from node api

I have the following mongodb document:
{
"_id" : ObjectId("5ee95b41ca023a3deb252ef2"),
"uid" : "jdoe",
"name" : "John Doe",
"employee_hire_date" : "2012-06-20",
"three_month_review_target" : "2012-09-20",
"six_month_review_target" : "2012-12-20",
"three_month_review_status" : {
"is_scheduled" : null,
"is_team_member_emailed" : null,
"is_review_executed" : null,
},
"six_month_review_status" : {
"is_scheduled" : null,
"is_team_member_emailed" : null,
"is_review_executed" : null,
}
}
I would like to update the three_month_review_status.is_scheduled nested property to true. I am using a put request to accomplish this:
const mongoose = require('mongoose');
const Reviews = require('../modules/reviews/models/reviews');
module.exports = (app) => {
app.put('/api/reviews/isScheduled', async (req, res) => {
console.log('body', req.body)
console.log('uid', req.body.uid)
console.log('is_scheduled', req.body.three_month_review_status.is_scheduled)
Reviews.findOneAndUpdate( { 'uid': req.body.uid }, { $set: { 'three_month_review_status.is_scheduled': req.body.is_scheduled }}, (err, result) => {
if (err) {
console.log('error', err)
}
else {
console.log('updated', result);
res.status(200).send(result);
}
} )
});
}
To test this, I execute this PUT request through Postman with the following request body:
{
"uid": "jdoe",
"three_month_review_status": {
"is_scheduled": "true"
}
}
However, when the request gets executed, the other two nested objects are removed and is_scheduled remains null. This is the document after the request is executed:
{
"_id" : ObjectId("5ee95b41ca023a3deb252ef2"),
"uid" : "jdoe",
"name" : "John Doe",
"employee_hire_date" : "2012-06-20",
"three_month_review_target" : "2012-09-20",
"six_month_review_target" : "2012-12-20",
"three_month_review_status" : {
"is_scheduled" : null
},
"six_month_review_status" : {
"is_scheduled" : null,
"is_team_member_emailed" : null,
"is_review_executed" : null,
}
}
What am I doing wrong? Here is my reviewsSchema for more context:
const { Schema, model } = require('mongoose');
const reviewsSchema = new Schema({
uid: String,
name: String,
employee_hire_date: String,
three_month_review_target: String,
six_month_review_target: String,
three_month_review_status: {
is_scheduled: Boolean,
is_team_member_emailed: Boolean,
is_review_executed: Boolean,
},
six_month_review_status: {
is_scheduled: Boolean,
is_team_member_emailed: Boolean,
is_review_executed: Boolean,
},
})
const Reviews = model('review', reviewsSchema);
module.exports = Reviews;
In Mongoose you don't need to specify the $set. Also based on the sample JSON that you send from the postman instead of req.body.is_scheduled you need to provide req.body.three_month_review_status.is_scheduled in the query. Also, need to add {new: true} if you want the findOneAndUpdate to return the updated document
So you can update the query like
Reviews.findOneAndUpdate(
{ uid: req.body.uid },
{
"three_month_review_status.is_scheduled":
req.body.three_month_review_status.is_scheduled,
},
{ new: true },
(err, result) => {
if (err) {
console.log("error", err);
} else {
console.log("updated", result);
res.status(200).send(result);
}
}
);

find nested (embedded) object in the collection

while i was going through my problem on StackOverflow,i noticed same question was asked before aswell,but none of them had got a good response,or an actual answer.
Mongoose Find One on Nested Object
How can I find nested objects in a document?
well back to my question: i wanted to find the object that is nested in the schema. trying findMany gives all the objects,and findOne give just the first one,but i want particular objects whose id i pass through req.body.checkbox.
my JS code goes like..
app.post("/data", uploads, function (req, res) {
User.findById(req.user.id, function (err, foundUser) {
if (err) {
console.log(err);
} else {
if (foundUser) {
var checkedBox = req.body.checkbox;
console.log(checkedBox);
User.findMany({_id:foundUser._id},{comments:{$elemMatch:{_id:checkedBox}}} ,function(err,checkedobj){
if(err){
console.log(err);
}
else{
console.log(checkedobj.comments);
if (Array.isArray(checkedobj.comments)) {
res.render("checkout",{SIMG: checkedobj.comments});
} else {
res.render("checkout",{SIMG: [checkedobj.comments]});
}
}
})
}
}
});
});
here is my schema,for reference
const commentSchema = new mongoose.Schema({
comment: String,
imagename: String,
permission:{type:Number,default:0},
});
const Comment = new mongoose.model("Comment", commentSchema);
const userSchema = new mongoose.Schema({
firstname: String,
lastname: String,
email: String,
password: String,
comments: [commentSchema],
permission:{type:Number,default:0},
});
userSchema.plugin(passportLocalMongoose);
const User = new mongoose.model("User", userSchema);
example
{
"_id" : ObjectId("5ec3f54adfaa1560c0f97cbf"),
"firstname" : "q",
"lastname" : "q",
"username" : "q#q.com",
"salt" : "***",
"hash" : "***",
"__v" : NumberInt(2),
"comments" : [
{
"permission" : NumberInt(0),
"_id" : ObjectId("5ec511e54db483837885793f"),
"comment" : "hi",
"imagename" : "image-1589973477170.PNG"
}
],
"permission" : NumberInt(1)
}
also when i check 3 checkboxes, console.log(checkBox) logs:
[
'5ec543d351e2db83481e878e',
'5ec589369d3e9b606446b776',
'5ec6463c4df40f79e8f1783b'
]
but console.log(checkedobj.comments) gives only one object.
[
{
permission: 0,
_id: 5ec543d351e2db83481e878e,
comment: 'q',
imagename: 'image-1589986259358.jpeg'
}
]
When you want multiple matching elements from an array you should use $filter aggregation operator
And as a precaution, first check req.body.checkbox is an array or not and convert it into an array of ObjectIds
app.post("/data", uploads, function (req, res) {
var ObjectId = mongoose.Types.ObjectId;
User.findById(req.user.id, function (err, foundUser) {
if (err) {
console.log(err);
} else {
if (foundUser) {
var checkedBox = req.body.checkbox;
if (!Array.isArray(checkedBox)) {
checkedBox = [checkedBox]
}
console.log(checkedBox);
var checkedBoxArray = checkedBox.map(id => ObjectId(id))
User.aggregate([
{$match: {_id: foundUser._id}},
{
$project: {
comments: {
$filter: {
input: "$comments",
as: "comment",
cond: { $in: [ "$$comment._id", checkedBoxArray ] }
}
}
}
}
],function(err,checkedobj){
if(err){
console.log(err);
}
else{
console.log(checkedobj[0].comments);
if (Array.isArray(checkedobj[0].comments)) {
res.render("checkout",{SIMG: checkedobj[0].comments});
} else {
res.render("checkout",{SIMG: [checkedobj[0].comments]});
}
}
})
}
}
});
});
Working example - https://mongoplayground.net/p/HnfrB6e4E3C
Above example will return only 2 comments matching the ids
You can make use of findById() method, more documentation about it is provided here
You can use something like this to search by object id:-
var id = "123";
userSchema.findById(id, function (err, user) { ... } );
Hope this helps!

Can't get figure it out: mongoose update a doc inside an array inside a doc

Well I want to update a doc inside an array by the index. My schema and code look like this
var userSchema = mongoose.Schema({
email: {
type: String,
required: false,
minlength: 4,
trim: true
},
bla: []
});
// this is the example object that I get from the db
{
"id" = "somethingUniq",
"email" = "this#email.workdamnit",
"bla" = [{"blabla": "something"},{"blabla":"somethingelse"}]
}
User.findOneAndUpdate({ "_id": "somethingUniq", "bla.1.blabla": "something" },
{
"$set": { "bla.$.blabla": "something new" },
function(err, doc) {
if (err) console.log('err in update', err);
console.log('doc', doc)
}
})
So that blabla with index of 1 gets updated. I have tried for hours now, and can't get anything I try to work...
The end result should look like :
{
"_id" = "somethingUniq",
"email" = "this#email.workdamnit",
"bla" = [{"blabla": "something"},{"blabla":"something new"}]
}
The $push operator appends a specified value to an array.
( https://docs.mongodb.com/manual/reference/operator/update/push/ )
Try $set in your case :
User.findOneAndUpdate(
{ "_id": "somethingUniq",
"bla.1.blabla": "something"
},
{ $set:{
"bla.$.blabla": "something new"
}
},
function(err,result){
if (!err) {
console.log(result);
}
});
And make sure you're using the correct one between _id and id because you're showing both of them in your subject.
In the case you're looking for id, change also :
"_id": "somethingUniq"
To :
"id": "somethingUniq"

Mongodb: Cannot see data of the Embedded Document via command

For mongodb's embedded document, I don't know why the data is not saved in the database or something else might be wrong? I tried to print out everything to make sure it works till the last step. But still got nothing when querying the embedded document, as you can see from below.
My schema:
// create competitorAnalysisSchema
var CompetitorAnalysis = new Schema({
firstObservation: { type: String },
secondObservation: { type: String },
thirdObservation: { type: String },
brandName: { type: String },
productCategory: { type: String },
photo1: { data: Buffer, contentType: String },
photo2: { data: Buffer, contentType: String },
photo3: { data: Buffer, contentType: String },
photo4: { data: Buffer, contentType: String }
});
// create UserSchema
var UserSchema = new Schema({
userName: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
currentDemo: { type: String },
nextDemo: { type: String },
startTime: { type: String },
startLocation: { type: String },
arriveTime: { type: String },
arriveLocation: { type: String },
leaveTime: { type: String },
leaveLocation: { type: String },
competitorAnalysis: [CompetitorAnalysis],
created_at: Date,
updated_at: Date
});
var User = mongoose.model('User', UserSchema);
module.exports = User;
In my index.js, all debug messages can be successfully printed out.:
// on routes that end in /users/competitorAnalysisTextData
// ----------------------------------------------------
router.route('/users/competitorAnalysisTextData/:userName')
// update the user info (accessed at PUT http://localhost:8080/api/users/competitorAnalysisTextData)
.put(function(req, res) {
// use our user model to find the user we want
User.findOne({ userName: req.params.userName}, function(err, user) {
if (err)
res.send(err);
console.log('Got the user!');
// update the text data
user.competitorAnalysis.firstObservation = req.body.firstObservation;
user.competitorAnalysis.secondObservation = req.body.secondObservation;
user.competitorAnalysis.thirdObservation = req.body.thirdObservation;
user.competitorAnalysis.brandName = req.body.brandName;
user.competitorAnalysis.productCategory = req.body.productCategory;
console.log('req.body.firstObservation: %s', req.body.firstObservation);
console.log('user.competitorAnalysis.firstObservation: %s', user.competitorAnalysis.firstObservation);
console.log('Save the text data for competitorAnalysisTextData!');
// save the user
user.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'User updated!' });
console.log('user.competitorAnalysis.firstObservation: %s', user.competitorAnalysis.firstObservation);
console.log('Finally save the User!');
});
});
})
As in console:
Got the user in "Put"!
req.body.firstObservation: 3
user.competitorAnalysis.firstObservation: 3
Save the text data for competitorAnalysisTextData!
user.competitorAnalysis.firstObservation: 3
Finally save the User!
Problem
However, when I search in my mongodb database, there is no data saved for the embedded document:
...
"leaveTime" : "Your Current Time:\n 2016-08-23 10:27:45 AM",
"leaveLocation" : "Your Current Address:\n 1\nInfinite Loop\nCupertino\n95014",
"competitorAnalysis" : [ ]
}
> db.users.find({"competitorAnalysis.firstObservation" : "3"}).pretty()
>
Empty here!
I'm new to mongodb. It'll be great if I can get some hints on where else I can check or what the problem might be.
Update
Output of collection:
> db.users.find().pretty()
{
"_id" : ObjectId("57ba5f41ad8858305a5d3e58"),
"created_at" : ISODate("2016-08-22T02:11:13.968Z"),
"updated_at" : ISODate("2016-08-24T19:42:56.311Z"),
"nextDemo" : "12:00pm - 3:00pm, Whole Foods Market, 5880 Centre Ave, Pittsburgh PA 15206",
"currentDemo" : "9:00am - 1:00pm, Whole Foods Market, 5880 Centre Ave, Pittsburgh PA 15206",
"password" : "<3da4dafc c96e05cd 855da8b3 ff0bf074 8156ec4b b9f1a002 ba907bcc d5e4aa5b fcd2fef9 dec240cd 86489978 7d85cec8 f11eae1c 7b60b2cc 6693da1a 4eae3a73>",
"email" : "chenya#gmail.com",
"userName" : "Chenya",
"__v" : 1,
"startLocation" : "Your Current Address:\n 10141\nBilich Pl\nCupertino\n95014",
"startTime" : "Your Current Time:\n 2016-08-24 03:42:42 PM",
"arriveTime" : "Your Arriving Time:\n 2016-08-24 03:42:44 PM",
"arriveLocation" : "Your Arriving Address:\n 10131\nBilich Pl\nCupertino\n95014",
"leaveTime" : "Your Current Time:\n 2016-08-23 10:27:45 AM",
"leaveLocation" : "Your Current Address:\n 1\nInfinite Loop\nCupertino\n95014",
"competitorAnalysis" : [ ]
}
>
These statements are the problem:
user.competitorAnalysis.firstObservation = req.body.firstObservation;
user.competitorAnalysis.secondObservation = req.body.secondObservation;
user.competitorAnalysis.thirdObservation = req.body.thirdObservation;
user.competitorAnalysis.brandName = req.body.brandName;
user.competitorAnalysis.productCategory = req.body.productCategory;
You're treating your competitorAnalysis array as if it were an object.
I don't work with Mongoose, so don't know the syntax, but you want to do something like this instead:
user.competitorAnalysis.push({
firstObservation: req.body.firstObservation,
secondObservation: req.body.secondObservation,
thirdObservation: req.body.thirdObservation,
brandName: req.body.brandName
productCategory: req.body.productCategory
});

Resources