Push To Mongoose Subdocuments - javascript

Hello I am creating a series of groupings describing the roles certain users are taking within the context of helping a client. The object in the Prospect model is called caseworkers. In caseworkers is a series of arrays for the different types of roles done. The equation is to allow the user to push his info as a subdocument called CaseWorker. Basically creating an object with 6 arrays that users can push to. Ive tried a few things and settled on Subdocuments. Any help would be awesome.
Here is my code:
const mongoose = require("mongoose");
const CaseWorker = require("./CaseWorker");
const ProspectSchema = mongoose.Schema({
caseWorkers: {
originators: [CaseWorker.schema],
loanProcessors: [CaseWorker.schema],
documentProcessors: [CaseWorker.schema],
upsells: [CaseWorker.schema],
primaryReso: [CaseWorker.schema],
taxPreparers: [CaseWorker.schema],
secondaryReso: [CaseWorker.schema],
}
module.exports = mongoose.model("prospect", ProspectSchema);
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const CaseWorkerSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
role: { type: String },
resoCred1: { type: String },
resoCred2: { type: String },
reminders: [
{
_id: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
userReminded: { type: mongoose.Schema.Types.ObjectId },
reminderDate: { type: Date },
reminderDueDate: { type: Date },
status: { type: String },
daysTilDue: { type: Number },
id: { type: String },
text: { type: String },
clientId: { type: mongoose.Schema.Types.ObjectId, ref: "Prospect" },
},
],
});
module.exports = mongoose.model("caseWorker", CaseWorkerSchema);
router.put("/:_id/caseWorkers/loanProcessors", auth, async (req, res) => {
const prospect = await Prospect.findByIdAndUpdate(req.params._id, {
"$push": {
"loanProcessors": {
"caseWorker": {
"name": req.body.name,
"email": req.body.email,
"role": req.body.role,
"resoCred1": req.body.resoCred1,
"resoCred2": req.body.resoCred2,
},
},
},
});
res.json(prospect);
console.log(prospect);
});

In your approach when updating the document you put caseWorker under loanProcessors but it's declared in the schema the other way around.
To update a nested object you have to use the dot notation to reference the field.
Don't forget to put the object key that represent the field as a string like this "caseWorkers.loanProcessors", because caseWorkers.loanProcessors is an invalid object key in javascript
"$push": {
"caseWorkers.loanProcessors": {
"name": req.body.name,
"email": req.body.email,
"role": req.body.role,
"resoCred1": req.body.resoCred1,
"resoCred2": req.body.resoCred2,
},
},

Related

How to query a mongo document using mongoose?

MongoDB documents contain an array of objects and what is the best way to query those documents if I want to find and remove an object from an array with some specific value;
Here is an example of the document schema
const mongoose = require("mongoose");
const LibrarySchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
books: [
{
type: new mongoose.Schema({
bookName: {
type: String,
required: true,
},
chapterReading: {
type: Number,
default: 1,
required: true,
},
}),
},
],
});
const Library = mongoose.model("Library", LibrarySchema);
exports.Library = Library;
If I want to find and remove a book with some bookName
Use $pull
Example :
Library.update({}, { $pull: { books: { bookName: "Yourbookname" } } })

findByIdAndUpdate pull from array objects equal to a specific value

I want to remove one or more objects of type tweet from the timeline list within the user model. The tweet objects that I want to remove are those whose author id matches a specific id user._id.
I have tried this:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of currentUser.timeline) {
if (tweet.author._id.equals(user._id)) {
currentUser.timeline.pull(tweet._id)
}
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));
and this:
await User.findbyIdAndUpdate(currentuser._id, { $pull: { timeline: { author : user._id } } }
but none of them are working.
My user model:
const userSchema = new Schema({
name: {
type: String,
required: true
},
biography: { type: String, maxlength: 160 },
location: {type: String, maxlength: 30 },
email: {
type: String,
unique: true,
required: true
},
image: {
url: String,
filename: String,
},
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }],
tweets: [{ type: Schema.Types.ObjectId, ref: "Tweet"}],
timeline: [{ type: Schema.Types.ObjectId, ref: "Tweet"}]
});
My tweet model :
const tweetSchema = new Schema({
images: [{
url: String,
filename : String
}],
text: { type: String, maxlength: 260},
date: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId, ref: "User" },
parent: { type: Schema.Types.ObjectId, ref: "Tweet", default:null },
replies: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
likes: [{ type: Schema.Types.ObjectId, ref: "User" }],
retweets: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
retweetStatus: {type: Schema.Types.ObjectId, ref: "Tweet", default: null}
});
If your collection looks like this:
[
{
"_id" : ObjectId("60254276259a60228cbe5707"),
"name" : "Mary",
"timeline" : [
ObjectId("60254276259a60228cbe5703"),
ObjectId("60254276259a60228cbe5704"),
ObjectId("60254276259a60228cbe5705")
]
},
{
"_id" : ObjectId("60254276259a60228cbe5706"),
"name" : "Dheemanth",
"timeline" : [
ObjectId("60254276259a60228cbe5700"),
ObjectId("60254276259a60228cbe5701"),
ObjectId("60254276259a60228cbe5702")
]
}
]
then the solution is:
usersSchema.updateOne(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
}
)
.then()
.catch()
// or
usersSchema.findOneAndUpdate(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
},
{
new: true
}
)
.then()
.catch()
I finally found the issue! The problem I was having is that I was trying to remove items from a list of objects while looping through that list. The solution is easy: you can just create an auxiliar empty array and push the items that you want to remove, then loop through that auxiliar array and pull the items from the original array.
In my case, I've already had an array with the tweets that I wanted to remove, user.tweets. The solution is:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of user.tweets) {
currentUser.timeline.pull(tweet._id)
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));

use mongooseModel.find() on schema that has a Schema.Types.Mixed property

I have trouble using postModel.find() query in a schema that defined as Schema.Types.Mixed.
this is a sample of my schema
const PostSchema = new mongoose.Schema({
//.....
address: {
type: String,
required: true,
},
postDetails: {
type: Schema.Types.Mixed,
required: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
},
//.....
});
this is a sample document stored in db
{
//.....
"state": "Lakes State",
"address": "some address",
"postDetails": {
"type": "Cages",
"condition": "Used"
},
//......
}
it is giving me an empty array if I use this
const queryObject = {
postDetails: {
type: 'Cages',
},
};
return this.postModel.find(queryObject);
but it gives the desired results if I include all the properties like this
const queryObject = {
postDetails: {
type: 'Cages',
condition: 'Used',
},
};
return this.postModel.find(queryObject);
How do i get all matching posts that have postDetails.type = 'Cages' ? without knowing all available properties inside postDetails
there are some similar questions about this here. but most of them are using arrays instead of an object
You can use dot notation for querying embedded documents
postModel.find({
"postDetails.type": "Cages"
});

Mongoose node return multiple arrays of referenced objects from document

I am working on a node backend API with mongoose. I have 2 schemas one User schema and one Follow schema(saved as users and follows in mongo). The follow schema fields followers and following hold an array of ObjectIds that refer to User objects. I am trying to return the Referenced objects in in the arrays so that I can respond with an object to the client that contains an array of userFollowing and userFollowers containing user objects, a but I am unable to populate the output.
I will also need to be able to filter the returned objects to only return 'username bio image email first_name surname join_date'.
My current incorrect output is below. I am not sure if its an error in my query or if I am using the correct approach.
[ { _id: 5c7dc1b92f3f1dd8ad9df993,
user: 5c7d93b57a29ce05a096c492,
userFollowing: [],
userFollowers: [] } ]
var mongoose = require('mongoose');
let Schema = mongoose.Schema;
var FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}, { toJSON: { virtuals: true } }
);
module.exports = mongoose.model('Follow', FollowSchema);
// username must be unique and is required
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
email: {
type: String,
unique: true,
},
first_name: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
join_date: {
type: Date,
default: Date.now
},
bio: {
type: String,
default: 'Tell me about yourself'
},
image:{
type: String,
default: 'profile.jpg'
},
password: {
type: String,
required: true
}
});
User.findOne({
'username': username
}, function (err, user) {
if (!user) {
return res.json({
'state': false,
'msg': `No user found with username ${username}`
})
} else {
const user_id = user._id;
Follow.aggregate([{
$match: {
"user": mongoose.Types.ObjectId(user_id)
}
},
{
$lookup: {
"from": "follows",
"localField": "following",
"foreignField": "_id",
"as": "userFollowing"
}
},
{
$lookup: {
"from": "follows",
"localField": "followers",
"foreignField": "_id",
"as": "userFollowers"
}
}, {
$project: {
"user": 1,
"userFollowers": 1,
"userFollowing": 1
}
}
]).exec(function (err, doc) {
console.log(doc);
res.json({
'state': true,
'msg': 'Follow list',
'doc': doc
})
})
}
})

Can't get populate() to fill out array in Mongoose

Let me begin by saying I know that this seems to be a frequently asked question and I've spent a couple of days trying to figure it out but no answer seems to work so I'm trying on here.
I have two models, User and Chapter: a Chapter can have have many members (Users). When I do router.get('/chapters') I want to see an array of all the Users associated with a Chapter as a property along with the other Chapter properties, like so:
[
{
"leads": [],
"members": [
{"_id":"someString1","firstName":"...", "lastName":"..."},
{"_id":"someString2","firstName":"...", "lastName":"..."},
],
"date": "2018-12-12T15:24:45.877Z",
"_id": "5c11283d7d13687e60c186b3",
"country": "5c11283d7d13687e60c185d6",
"city": "Buckridgestad",
"twitterURL": "qui",
"bannerPic": "http://lorempixel.com/640/480/people",
"__v": 0
}
]
But what I'm getting is this:
[
{
"leads": [],
"members": [],
"date": "2018-12-12T15:24:45.877Z",
"_id": "5c11283d7d13687e60c186b3",
"country": "5c11283d7d13687e60c185d6",
"city": "Buckridgestad",
"twitterURL": "qui",
"bannerPic": "http://lorempixel.com/640/480/people",
"__v": 0
}
]
These are my Schemas:
Chapter
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const ChapterSchema = new Schema({
country: {
type: Schema.Types.ObjectId,
ref: "countries"
},
city: {
type: String,
required: true
},
leads: [
{
type: Schema.Types.ObjectId,
ref: "users"
}
],
members: [
{
type: Schema.Types.ObjectId,
ref: "users"
}
],
twitterURL: {
type: String,
required: true
},
bannerPic: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now()
}
});
module.exports = Chapter = mongoose.model("chapters", ChapterSchema);
User
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const UserSchema = new Schema({
username: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
organisation: {
type: String,
required: true
},
chapter: {
type: Schema.Types.ObjectId,
ref: "chapters"
},
email: {
type: String,
required: true
},
admin: {
type: Boolean,
default: false
},
lead: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now()
}
});
module.exports = User = mongoose.model("users", UserSchema);
Like I said, when I call the endpoint, I want it to return me all the Chapters with all the Users as a populated property.
I've tried a lot of variations of .populate() but to know luck. The closest I got was going through the early levels of callback hell which I know isn't necessary with today's tech, but nothing is working!
My routes/api/chapters.js
// #route GET api/chapters
// #desc Get all Chapters
// #access Public
router.get("/", (req, res) => {
Chapter.find()
.populate("members")
.then(chapters => {
return res.json(chapters);
})
.catch(err =>
res.status(404).json({ nochaptersfound: "No Chapters found" })
);
});
I can get it to work the other way around:
My routes/api/users.js
// #route GET api/users
// #desc Return all users
// #access Public
router.get("/", (req, res) => {
User.find()
.populate("chapter")
.exec()
.then(users => res.status(200).json(users))
.catch(err => console.log(err));
Returns a user with the populated Chapter, but I can't populate the chapter.members array
Any help would be greatly appreciated!
Thanks!!
From your comment, I believe you are not actually storing users in your chapters. What you are doing is this:
User.create({..., chapter: id})...
And assuming chapter now has a user. Its not the way it works with mongoose, so if you want to actually save in both place, you will need to do it yourself. You are thinking about this as if it were a relational database
You will need to do something like:
const user = await User.create({..., chapter: id})
const chapter = await Chapter.findOne({ _id: id })
chapter.members.push(user)
chapter.save()
If your populate wasn't working, you'd not get an empty array, you'd get an array with ids. Your current populate query is fine, you just don't have any data to populate
With promises, it would look like this:
var userPromise = User.create({..., chapter: id}).exec()
var chapterPromise = Chapter.findOne({ _id: id }).exec()
Promise.all([userPromise, chapterPromise]).then((user, chapter) => {
chapter.members.push(user)
return chapter.save()
}).then(chapter => {
// send response
})
If you need 10 chapters with 10 to 50 users, I'd create 50 users, then push all of them into the chapters and save the chapter.

Categories

Resources