Find and update double nested referenced document - javascript

I have 3 models, which are referenced with one another, I want to search for the first document based on a query (this will always exist and return a result), once that returns to search for the second one which is referenced to the first one (which may or may not exist), and push into the 3rd one which is referenced in the 2nd one.
User (Parent Model).
Location (Child of User)
Reports (Child of Location)
I want something similar to the following:
Search User (findById), search for Location using query (search for locationName) - if it exists update it and also push into Reports, else if Location doesn't exist, create one and push into Reports.
Parent Model:
const User = mongoose.model(
"User",
new mongoose.Schema({
firstName: String,
lastName: String,
dateOfBirth: Date,
email: String,
password: String,
verified: Boolean,
locations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Locations"
}
]
})
);
Child Model:
const Locations = mongoose.model(
"Locations",
new mongoose.Schema({
address: String,
adress2: String,
city: String,
state: String,
country: String,
zip: String
reports: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Reports"
}
]
})
);
Child of Child Model:
const Reports = mongoose.model(
"Reports",
new mongoose.Schema({
severity: String,
note: String,
date: Date,
})
);
Any help is greatly appreciated!

I was able to figure this out, by first setting the report values, setting the location values, saving the report, adding the report number to the location value, then searching for a location based on location ID using fineOneAndUpdate:
Location.findOneAndUpdate(
{ _id: req.body.locationID},
{
$push: {
reports: report
}
},
(err, result) => {
if (err) {
res.status(500).send({ message: err });
return;
};
if (!result) {
location.save((err, done) => {
if (err) {
res.status(500).send({ message: err });
return;
};
User.findByIdAndUpdate(
{ _id: req.body.id },
{
$push: {
locations: done
}
},
(err, doc) => {
if (err) {
res.status(500).send({ message: err });
return;
};
res.status(200).send({ message: 'Successfully added new location with report.' });
return;
})
})
}
if (result) {
res.status(200).send({ message: 'Successfully updated existing location with report.' });
}
}
);

Related

How to retreive an object from an array of Objects in mongodb given a objectid

I have an array of reviews, I want to retrieve only a review from an array of objects inside a schema.
Here is my schema:
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,
},
},
],
});
How can I get a single review from the array if 'by' will be req.user._id. I have the previous code, but it is not working to retrieve the review only that satisfies the query.
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const review = await Seller.findOne(
{ _id: seller_id },
{ reviews: { $elemMatch: { by: req.user._id } } } //get the review that matches to the user_id
);
res.status(200).send(review);
} catch (err) {
//sends the status code with error code message to the user
res.status(502).send({
error: "Error retreiving review.",
});
}
This retrieves the whole seller document, but I just want to retrieve the object review with the given a user_id === by: ObjectID
give it a try
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const review = await Seller.findOne(
{ _id: seller_id , "reviews.by": req.user._id}, { "reviews.$": 1 }
);
res.status(200).send(review);
}
Try to cast also the user _id as ObjectId and unify your conditions into a single object:
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const user_id = mongoose.Types.ObjectId(req.user._id);
const review = await Seller.findOne({
_id: seller_id,
reviews: { $elemMatch: { by: user_id } },
});
res.status(200).send(review);
} catch (err) {
//sends the status code with error code message to the user
res.status(502).send({
error: 'Error retreiving review.',
});
}

Mongoose - How to update all objects within an array in MongoDB?

I am creating an app where I want to toggle the default address of a person.
My User Schema contains a field which is an array of delivery addresses which are objects.
One of the fields is isDefault, which is the defaultAddress field I want to toggle from true to false and vice versa.
Specifically, if the user changes their default address, I want to set the rest of the addresses to false and update the one he/she chose to be true
User Schema
const UserSchema = Schema({
email: {
type: String,
required: true,
},
deliveryAddresses: [deliverySchema]
Delivery Address Schema
{
id: {
type: Schema.Types.ObjectId,
required: true
},
isDefault: {
type: Boolean,
required: true,
default: false,
},
name: {
type: String,
},
email: {
type: String,
},
phone: {
type: String,
},
}
To do that, what I have done so far is:
Getting the email of the user.
Getting the id of the delivery address from the user that will be toggled to true.
exports.setDefault = (req, res, next) => {
const email = req.body.email;
const id = req.body.id;
User.findOne({ email: email })
.then((user) => {
let addresses = [...user.deliveryAddresses];
addresses.forEach((address) => {
address.isDefault = false;
});
const index = addresses.findIndex((address)=> {
return address.id.toString() === id.toString();
});
addresses[index].isDefault = true;
user.deliveryAddresses = addresses;
return user.save();
})
.then((doc) => {
res.status(200).json({
user: doc,
statusCode: "200",
msg: "Address updated successfully",
});
})
.catch((err) => {
res.status(500).json({
statusCode: 500,
error: err,
msg: "Something went wrong",
});
});
};
However, after doing all this, on testing my api on postman, it seems to work, no errors.
But on checking the database, nothing has changed.
I am at loss as to what I'm doing wrong.
Mongoose is weird. You need to mark the deliveryAddresses sub-object as modified, otherwise its changes won't be saved.
user.markModified('deliveryAddresses');
user.save();
From Mongoose FAQs
"Mongoose doesn't create getters/setters for array indexes; without
them mongoose never gets notified of the change and so doesn't know to
persist the new value. There are two workarounds: MongooseArray#set or
Document#markModified()."
Can you try sth like
user.markModified('deliveryAddresses');
user.save();
More on https://mongoosejs.com/docs/faq.html

Mongoose - When returning all items from a collection (with no search param) the returned items from the collection do not contain their mongo _id

I am having a bit of an issue with Mongoose/MongoDB this afternoon. I have a situation where I need to return all items from a collection, and doing so means that I do not pass in any search params to mongoose.find().
This is the controller that handles the get all request:
exports.get_all_posts = async (req, res, next) => {
const { params } = req;
const { sortby } = params;
//Sortby param takes two arguments for now: most_recent, oldest
try {
const getAllPosts = await BlogPost.find({}, { _id: 0 });
console.log(getAllPosts);
if (!getAllPosts) throw new Error('Could not get blog posts.');
res.json({
posts: date_.sort(getAllPosts, sortby)
});
} catch (error) {
next(error);
}
};
This is particularly where I think the issue is coming from:
const getAllPosts = await BlogPost.find({}, { _id: 0 });
I am passing an empty search parameter and then removing the _id so that it doesn't throw an error telling me that I need to provide the _id.
However I still need to be able to pull in all of the posts. My items from this collection return as normal, just without their _id's.
Here is my model for the blog posts:
const mongoose = require('mongoose');
const BlogPostSchema = new mongoose.Schema({
date: {
type: Date,
required: true
},
title: {
type: String,
required: true
},
author: {
type: String,
required: true
},
likes: {
type: Number,
required: false
},
post_body: {
type: String,
required: true
},
description: {
type: String,
required: true
},
tags: [
{
type: String,
required: false
}
],
featuredImage: {
type: String,
required: false
},
draft: {
type: Boolean,
required: true
}
});
module.exports = mongoose.model('BlogPost', BlogPostSchema);
One thing to note is that I have not defined an _id. Mongoose automatically adds in the _id field before saving a schema, so I think it is okay without it, as it has been in the past.
Thanks in advance for reading and any input!
Just as Joe has commented, { _id: 0 } as the second parameter is making your query not return the _id field.
Also as he said, there should be no problem whatsoever with using find({}).
Since other than what has already been stated, I couldn't figure out any mistake in the code snippets you provided, I guess this error could be coming from somewhere else in your project.
exports.get_all_posts = async (req, res, next) => { const { params } = req; const { sortby } = params;
try { const getAllPosts = await BlogPost.find({}); console.log(getAllPosts); if (!getAllPosts) throw new Error('Could not get blog posts.'); res.json({ posts: date_.sort(getAllPosts, sortby) }); } catch (error) { next(error); } };
no need to {_id:0} in the find() method because this method retrieve all the documents in the db collection

Node get the first element in array of object

I'm trying to do some relations between my schemas and I have some problems with my solution.
user schema:
let userSchema = new mongoose.Schema({
username: { type:String, default:null },
gender: { type:String, default:null },
role: { type: mongoose.Schema.Types.ObjectId, ref: 'Role', required: true },
});
role schema:
let roleSchema = new mongoose.Schema({
name: { type:String, default:"Player" },
privileges:
[
{
resource: String ,
actions: [ String ]
},
],
});
and my query is
User.find({ "email":email }).populate({path: 'role', model: 'Role'}).limit(1).exec().
then(results => {
if (results.length) {
user = results[0];
console.log(user.role.privileges);
}
else {
throw new APIError("login_failed");
}
})
I need to access to the first element of privileges and Thank you.
The first parameter returned by Mongoose (and Node in general) is not your data. It's the error (if any). You need to write then( (error, results) => {
I need to access to the first element of privileges
privileges being an array, simply access the first element using :
user.role.privileges[0]
As an aside, you can also use Mongoose's .findOne() method, instead of .find().limit(1) :
User
.findOne({ email }) // Shorthand for { email : email }
.populate({path: 'role', model: 'Role'})
.exec()
.then( (error, user) => {
if(error || !user) throw new APIError("login_failed");
console.log(user.role.privileges[0]);
})

Populate Query Options with Async Waterfall

I'm trying mongoose populate query options but i don't know why the query options doesn't work.
I have user schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
username: { type: String, required: true },
email: { type: String },
name: { type: String },
address: { type: String }
},
{ timestamps: true }
);
module.exports = mongoose.model('User', UserSchema);
and feed schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const FeedSchema = new Schema(
{
user: { type: Schema.ObjectId, ref: 'User' },
notes: { type: String, required: true },
trx_date: { type: Date },
status: { type: Boolean, Default: true }
},
{ timestamps: true }
);
FeedSchema.set('toObject', { getters: true });
module.exports = mongoose.model('Feed', FeedSchema);
I want to find all feed by user id, i used async waterfall like the following code:
async.waterfall([
function(callback) {
User
.findOne({ 'username': username })
.exec((err, result) => {
if (result) {
callback(null, result);
} else {
callback(err);
}
});
},
function(userid, callback) {
// find user's feed
Feed
.find({})
// .populate('user', {_id: userid._id}) <== this one also doesn't work
.populate({
path: 'user',
match: { '_id': { $in: userid._id } }
})
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
With above code, i got all feeds, and it seems like the query option do not work at all, did i doing it wrong ?
Any help would be appreciate.
Not sure why you are looking to match "after" population when the value of _id is what is already stored in the "user" property "before" you even populate.
As such it's really just a simple "query" condition to .find() instead:
async.waterfall([
(callback) =>
User.findOne({ 'username': username }).exec(callback),
(user, callback) => {
if (!user) callback(new Error('not found')); // throw here if not found
// find user's feed
Feed
.find({ user: user._id })
.populate('user')
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
Keeping in mind of course that the .findOne() is returning the whole document, so you just want the _id property in the new query. Also note that the "juggling" in the initial waterfall function is not necessary. If there is an error then it will "fast fail" to the end callback, or otherwise pass through the result where it is not. Delate "not found" to the next method instead.
Of course this really is not necessary since "Promises" have been around for some time and you really should be using them:
User.findOne({ "username": username })
.then( user => Feed.find({ "user": user._id }).populate('user') )
.then( feeds => /* do something */ )
.catch(err => /* do something with any error */)
Or indeed using $lookup where you MongoDB supports it:
User.aggregate([
{ "$match": { "username": username } },
{ "$lookup": {
"from": Feed.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "feeds"
}}
]).then( user => /* User with feeds in array /* )
Which is a bit different in output, and you could actually change it to look the same with a bit of manipulation, but this should give you the general idea.
Importantly is generally better to let the server do the join rather than issue multiple requests, which increases latency at the very least.

Categories

Resources