$PUSH into a Subdoc array in mongoose - javascript

I have the following model:
var Customer = mongoose.model('Customer', {
firstname : String,
lastname : String,
phone : String,
street : String,
city : String,
state : String,
zip : String,
fixed : Boolean,
readings: [
{
reading: Number,
date: String
}],
billing: {
charges: [Number],
payments: [Number]
}
});
Im trying to add individual charges and payments to billing.
I am using the following code but doesn't seem to be updating....
Customer.update({_id: req.params.cust_id},
{ billing: { $push:{charges: 100}, $push:{payments:50}}},
{upsert:true},
function(err, customer){
if(err){
console.log(err);
}
else{
Customer.findOne({
_id : req.params.cust_id
}, function(err, customer) {
if (err)
res.send(err);
res.json(customer);
});
}
}
)
IF anyone has any suggestions they would be greatly appreciated. Ive found a lot of examples of $push but all seem to be on the top level and not a subdoc.

Use dot notation for the fields, which are an argument to $push
Customer.update({_id: req.params.cust_id},
{ $push: { "billing.charges": 100, "billing.payments": 50} },
{upsert:true},

Related

How to add an object to an array of object, using addToSet, or push operators in mongodb

I have an array of reviews, I want to add a review using addToSet that will check if user is present in the array, then we do not want to add since one user can only review once.
My schema looks like this:
const sellerSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
reviews: [
{
by: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
title: {
type: String,
},
message: {
type: String,
},
rating: Number,
imagesUri: [{ String }],
timestamp: {
type: Date,
default: Date.now,
},
},
],
});
I might be doing the query wrong, but can't figure out how to add a review and check if current user has not reviewed before.
Here is the query where I add the review:
router.post("/review/:_id/", async (req, res) => {
try {
const stylist_id = mongoose.Types.ObjectId(req.params._id);
const review = {
by: req.user._id,
title: req.body.title,
message: req.body.message,
rating: parseFloat(req.body.rating),
};
if (req.body.imagesUri) {
//if there is images, we need to set it up
review.imagesUri = req.body.imagesUri;
}
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
return res.status(200).send(true);
}catch(err){
res.status(502).send({
error: "Error creating a review.",
});
}
});
I'm thinking of checking for seller's id and also check that no review is by current user, but it is not working.
const userID = req.user._id;
await Seller.updateOne(
{ _id: seller_id, reviews: { $elemMatch: { by: { $ne: { userID } } } } },
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
ANSWER:
I was able to solve the issue, in case other people have same issue. I did this:
await Seller.updateOne(
{
_id: seller_id,
"reviews.by": { $nin: [req.user.id] },
//knowing req.user._id is a mongoose.Types.ObjectId.
//You can also use [id1, id2, ...] to the array to check for other id's
},
{ $addToSet: { reviews: review } } //get the review that matches to the user_id
);
Here is the documentation for $nin operator: https://www.mongodb.com/docs/manual/reference/operator/query/nin/
You are pushing the review object inside an object.
Instead do this:
await Seller.updateOne(
{ _id: seller_id },
{ $addToSet: { reviews: review } }
);

Unable to update Mongoose Nested Array

I've spent a day going through Mongoose documentation and posts on here and while I'm almost certain I have the code as prescribed in all those solved posts I simply cannot get my code to work. :(
The schema is relatively complex but by no means as complex as I've seen:
const hrUserSchema = new mongoose.Schema({
username: String,
displayName: String,
school: String,
leaveAuthoriser: String,
holidayLeft: Number,
holidayAllowed: Number,
discretionaryDays: Number,
emailAddress: String,
termTimeOnly: Boolean,
noOfAdditionalContractedDays: Number,
noOfAdditionalContractedDaysLeft: Number,
teachingStaff: Boolean,
employStartDate: String,
notes: String,
loginAttempts: Number,
lineManages: [{
staffName: String
}],
bookedHoliday: [{
dateEntered: String,
startDate: String,
endDate: String,
daysTaken: Number,
status: String,
approvedBy: String,
leaveType: String,
declineReason: String
}],
additionalContractedDays: [{
acdDateEntered: String,
acdStartDate: String,
acdEndDate: String,
acdDaysTaken: Number,
acdStatus: String,
acdApprovedBy: String,
acdDeclineReason: String
}],
perfMan: [{
year: Number,
selfReview: {
sref1: String,
sref2: String,
sref3: String,
sref4: String,
sref5: String,
status: String,
dateStarted: String,
dateCompleted: String,
signedOff: Boolean
},
stage1: {
objectives : [
{
objective: String,
objectiveLinkToSchoolTeam: String,
objectiveProgress: Boolean
}
],
personalDevelopment: String,
resourcesTraining: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
},
stage2: {
feedback: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
},
stage3: {
feedback: String,
appraiserSignOff: Boolean,
appraiseeSignOff: Boolean
}
}]
});
Basically I want to update perfMan.stage1.objectives.objectiveProgress, example of what data in that might look like is:
"perfMan" : [
{
"_id" : ObjectId("60cb502631dcea3eaaae6853"),
"selfReview" : {
"sref1" : "I have no strength",
"sref2" : "No developments",
"sref3" : "None I'm brill",
"sref4" : "The department has no aims",
"signedOff" : true
},
"stage1" : {
"objectives" : [
{
"_id" : ObjectId("60cb502631dcea3eaaae6854"),
"objective" : "Objective is pants",
"objectiveLinkToSchoolTeam" : "I hate objectives!",
"objectiveProgress" : false
},
{
"_id" : ObjectId("60cb502631dcea3eaaae6855"),
"objective" : "My second Objectoves",
"objectiveLinkToSchoolTeam" : "My Second reasons",
"objectiveProgress" : false
}
],
"personalDevelopment" : "My personal Development",
"resourcesTraining" : "My Resources"
},
"stage2" : {
"feedback" : "Keep working HARD",
"appraiserSignOff" : true
},
"stage3" : {
"feedback" : "Yoy've done really well",
"appraiserSignOff" : true
},
"year" : NumberInt(2021)
}
]
I've read about and tried to implement this by using arrayFilters which to me seems to exactly what I want, I've checked my Mongoose version (hosted) and it's 4.4.6 so I'm easily running above 3.6 which I think is what's needed.
My current code looks like this, I've confirmed the find is getting the right data:
HRUser.findOneAndUpdate(
{"username": username},
{"$set": {"perfMan.$[perfMan].stage1.objectives.$[objectives].objectiveProgress" : true}},
{"arrayFilters" : [{ "perfMan._id": ObjectId("" + perfmanID + "") },{ "objectives._id": ObjectId("" + objectiveID + "") }]}
),
function (err, response) {
console.log(err)
console.log(response);
if (!err) {
res.send("Successfully updated staff member details.");
} else {
res.send(err);
}
};
If somebody could spot my obviously glaring error I would be for ever grateful!
Thanks
After not looking at a screen all weekend and reading through the documentation for the billionth time I decided to change tack! I can't see any limitations on the number of arrayFilters, or for that matter objectID use within them, and I'm 99% certain both object IDs are correct but, after changing the code to the below, basically using a mix of a positional operator $ based on the search of the same first objectID AND an arrayFilter element it's now working! Code below:
HRUser.findOneAndUpdate({
perfMan: {
$elemMatch: {
_id: ObjectId("" + perfManID + "")
}
}
},
{"$set": {"perfMan.$.stage1.objectives.$[objectives].objectiveProgress" : false}},
{"arrayFilters" : [{ "objectives._id": ObjectId("" + objectiveID + "") }]},
function (err, response) {
console.log(err)
console.log(response);
});

aggregate then update to other collection

I want to insert the returned data of my aggregate (controller function) to another collection called User. Below is my schema for the User:
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'First name is required']
},
lastName: {
type: String,
required: [true, 'Last name is required']
},
email: {
type: String,
required: [true, 'Email is required']
},
loginType: {
type: String,
required: [true, 'Login type is required']
},
password: {
type: String
},
transactions: [{transactionId: String}, date:{type:Date, default:new Date()}],
totalIncome : [{id:String},{totalIncome:Number}]
})
module.exports = mongoose.model('User', userSchema);
I want to insert the result of this controller function into User collection as another variable
return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
totalIncome: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
return (err) ? false : totalIncome
})
The result of my aggregate above is this:
[
{
"_id": "Income",
"totalIncome": 18000
}
]
Please help :( I tried adding $out but it replaces the whole user collection.
I just want it to add to a specific user and update its content every time I use the controller function above. Thank you!
return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
amount: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
// return (err) ? false : totalIncome
console.log(totalIncome)
if (err) {
return false
} else {
User.findByIdAndUpdate(userId, {"$set" : {"totalIncome": totalIncome[0].amount }}, { "new": true })
.then((transaction, err ) => {
return (err) ? false : true
})
}
})
I was able to add totalIncome to my User collection by using findByIdAndUpdate() and $set operator. Feel free to provide different solution for my question above.

MongoDB index search works in console, but not from browser

All of this was working yesterday so I'm really not sure whats wrong. I'v searched on SO and been through the docs and steps a number of times but I havnt got it to work. Any help much appreciated as always!
If I type db.movies.find({ '$text': { '$search': 'elephant' } }) into the shell, it returns a list of titles with 'elephant' in the title. But if I run the same search from my browser it crashes the node server and I get MongoError: text index required for $text query.
The error comes from the error response from the find():
Movie.find(
{ '$text': { '$search': 'elephants' } },
(err, movies) => {
if (err) throw err; // MongoError: text index required for $text query
if (!movies) {
res.json({
data: null,
success: false,
message: 'No movies data'
});
} else {
res.json({
success: true,
data: movies
});
}
})
The index on my DB looks like this:
{
"v" : 2,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "title_text",
"ns" : "db.movies",
"weights" : {
"title" : 1
},
"default_language" : "english",
"language_override" : "language",
"textIndexVersion" : 3
}
I also have mongoose schemas defined and I'm using an auto increment plugin to increment a custom id field on each movie save. (Saving movies works, so I am connecting to the DB ok)
mongoose.model('Job', new Schema({
id: Number,
title: String,
desc: String,
genre: String
})
.index({ title: 'text' })
.plugin(autoIncrement.mongoosePlugin));
As pointed out in the comments by #Chris Satchell
Adding the index to the mongoose schema solves this.
instead of:
mongoose.model('Job', new Schema({
id: Number,
title: String,
desc: String,
genre: String
})
do this:
mongoose.model('Job', new Schema({
id: Number,
title: { type: String, text: true },
desc: String,
genre: String
})
even though I still had .index({ title: 'text' }) on the Schema, you still need to define the { type: String, text: true } on the property.

Mongodb: Can't append to array using string field name

i am trying to push inside a subarray using $push but got a Mongo error, and not able to get through this after considerable search on google, and findOneAndUpdate didn't worked out so i used find and update separately
{ [MongoError: can't append to array using string field name: to]
name: 'MongoError',
err: 'can\'t append to array using string field name: to',
code: 13048,
n: 0,
lastOp: { _bsontype: 'Timestamp', low_: 2, high_: 1418993115 },
Schema:
var NetworkSchema = new Schema({
UserID: {
type: Schema.Types.ObjectId,
ref: 'User'
},
NetworkList: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
NetworkRequest: [{
from: [{
type:Schema.Types.ObjectId,
ref: 'User'
}],
to: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}]
});
Document:
{
"UserID" : ObjectId("549416c9cbe0e42c1adb42b5"),
"_id" : ObjectId("549416c9cbe0e42c1adb42b6"),
"NetworkRequest" : [
{
"from" : [],
"to" : []
}
],
"NetworkList" : [],
"__v" : 0
}
Controller:
exports.update = function(req,res) {
var network = req.network;
var query={'UserID':req.body.UserID};
var update = {$push:{'NetworkRequest.to': req.body.FriendID}};
Network.find(query,function(err){
if (err) {
console.log(err);
return err;
} else {
}
});
Network.update(query,update,{upsert:true},function(err,user){
console.log(user);
if (err) {
console.log(err);
return err;
} else {
console.log('User'+user);
}
});
};
Everything #cbass said in his answer is correct, but since you don't have a unique identifier in your NetworkRequest element to target, you need to do it by position:
var query = {'UserID': req.body.UserID};
var update = {$push:{'NetworkRequest.0.to': req.body.FriendID}};
Test.update(query, update, {upsert: true}, function(err, result) { ... });
'NetworkRequest.0.to' identifies the to field of the first element of the NetworkRequest array.
Your query var query={'UserID':req.body.UserID}; identifies the document you want to edit. Then you need another query to identify which object in the NetworkRequest array that the UserID should be pushed into. Something like below:
var query = {
'UserID':req.body.UserID,
'NetworkRequest._id': ObjectId(someNetworkRequestId)
};
Then use this update query containing $ which is the index of the object in the nested array(NetworkRequest)
var update = {
$push:{
'NetworkRequest.$.to': req.body.FriendID
}
};

Categories

Resources