find nested (embedded) object in the collection - node.js

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!

Related

Mongoose save an array of objects in Ref schema

I've got the Parent Schema:
const parentSchema = new Schema({
name: {
type: String,
},
children: [{
type: Schema.Types.ObjectId,
ref: "Children"
}]
})
And this is the Children Schema:
const childrenSchema = Schema({
name: {
type: String
},
surname: {
type: String
}
})
I have an incoming user register POST request in the following format:
{
"name": "TEST",
"children" : [
{ "name":"test","surname": "test" },
{ "name":"test","surname": "test" }
]
}
Here's the router:
router.post("/register", (req, res, next) => {
const {name, children} = req.body;
let newParent = newParent({
name,
children
});
newParent.save((err, result) => {
// res.send(result) etc.
})
}
This results in the following error:
Cast to Array failed for value "[ { name: 'test', surname: 'test' } ]" at path "children"
How can I save all children and keep in the ref only the children _id so i can later populate the Parent collection?
The children field in the parent is expecting an arrays of ObjectIds but you are passing it an arrays of objects that do not conform to that expectation. Please try saving the children, getting the ids and then using those ids to populate the children field in parent document. Something like below:
children.save()
.then(results => {
childrenids = []
results.foreach[item => childrenids.push(result._id)]
newParent.children = chilrenids
newParent.save()
.then(results => res.send({results})
})
To save childData in Parents, You need to save first child's data in children schema Then get childIds and save to Parent Data.
Working Example:
let req = {
"name" : "TEST",
"children" : [
{ "name":"test","surname": "test" },
{ "name":"test","surname": "test" }
]
}
Children.collection.insert(req.children, function (err, docs) {
if (err){
conasolw.log(err);
} else {
var ids = docs.ops.map(doc=>{ return doc._id});;
console.log(ids);
let newParent = Parent({
name : req.name,
children : ids
});
newParent.save((err, result) => {
console.log('parent save');
console.log(err);
console.log(result);
})
}
});
Note :
Test on "mongoose": "^5.3.3"

Mongo Find is not working

This is my user schema
var UserSchema = new Schema({
Pcard: [{ type: Schema.Types.ObjectId, ref: 'Pcard' }]
})
These are id's saved in user Pcard array
"user": { // id 59560bc83e1fdc2cb8e73236
"Pcard": [
"595b43d16e4b7305e5b40845",
"595b459a6e4b7305e5b40848",
"595f48f58117c85e041f1e1c",
],
}
This is my Pcard Scema
var PcardSchema = new Schema({
Time : {
type : Date,
default: Date.now
},
})
I want to find user having Id and which also contains some id in Pcard array
User.find({ _id: req.user._id,
Pcard:{$in : [req.params.PcardId] }
}, function (err, Userpresent) {
if (err) {
res.json(code.Parked);
}
if (Userpresent === null || Userpresent === undefined) {
res.json(code.notAllowed);
}else{
This else is execting everytime.
}
}
});
when i querying with user which does not have a Pcardid in Pcard array it is still going in else condition !
for eg . i am querying with this id 59560bc83e1fdc2cb8e73236 and this not contain 5957bd177e996b56d08b991a in Pcard array but still it is going on else part of the user query.
If you are expecting only one user, use findOne.
Might be useful/cleaner for you to return early instead of having a bunch if-else.
User.findOne({
_id: req.user._id,
Pcard: { $in : [req.params.PcardId] }
}, function (err, user) {
if (err) {
return res.json(code.Parked);
}
// simplified: undefined and null are both falseys
if (!user) {
return res.json(code.notAllowed);
}
// user should be found at this point
});
Good read: All falsey values in JavaScript

Updating multiple sub-documents with Mongoose and Node

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

$unwind nested document and $match

I have a nested document which looks like:
var User = new Schema({
id: String,
position: [{
title: String,
applied:[{
candidate_id: String,
name: String
}],
}],
What I am looking to do is return all of the 'applied' subdocuments which match a certain 'candidate_id'
What I have so far:
app.get('/applied', function(req, res){
var position = "58dc2bd4e7208a3ea143959e";
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});
I have another function which returns all the positions that match, and that code works:
app.post('/search', function (req, res) {
var position = new RegExp(req.body.position, 'i');
var location = new RegExp(req.body.location, 'i');
User.aggregate(
{$unwind : "$position"},
{$match:{'position.title': position,'position.location':location}}).exec(function (err, result) {
console.log(result);
res.send({ results: result });
});
});
So basically I am struggling with getting a sub-sub-document. Any idea where I'm going wrong?
Sample data:
{
"_id" : ObjectId("58c2871414cd3d209abf5fc9"),
"position" : [
{
"_id" : ObjectId("58d6b7e11e793c9a506ffe8f"),
"title" : "Software Engineer",
"applied" : [
{
"candidate_id" : "58d153e97e3317291gd80087",
"name" : "Sample user"
},
{
"candidate_id" : "58d153e97e3317291fd99001",
"name" : "Sample User2"
}
]
},
{
"_id" : ObjectId("58c2871414cd3d209abf5fc0"),
"title" : "Software Engineer",
}
],
}
What is going on above is there 2 positions, one of which (first entry) has 2 applied candidates, What I need to do is return the nested object if it matches the mongoose query.
Your code seems fine to me I have implemented same and it works for me only possible issue can be that your position="58dc2bd4e7208a3ea143959e" it might be talking it as a string just convert it to objectId by using the following code and check it should work for you.
var mongoose = require('mongoose');
var position = mongoose.Types.ObjectId("58dc2bd4e7208a3ea143959e");
User.aggregate(
{$unwind : "$position"},
{$unwind : "$position.applied"},
{$match:{'position.applied.candidate_id': position}}).exec(function (err, result) {
console.log(result);
});
res.render('applied', { title: 'applied',layout:'candidate'});
});

How to use populate functionality by using populate or making inner query with aggregation in mongodb

I have following data in my Mongodb.
{
"_id" : ObjectId("54a0d4c5bffabd6a179834eb"),
"is_afternoon_scheduled" : true,
"employee_id" : ObjectId("546f0a06c7555ae310ae925a")
}
I would like to use populate with aggregate, and want to fetch employee complete information in the same response, I need help in this. My code is:
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
res.json({"message": "success", "data": response, "status_code": "200"});
}
);
The response i am getting is
{"message":"success","data":{"_id":"54a0d4c5bffabd6a179834eb","count":1},"status_code":"200"}
My expected response is:
{"message":"success","data":[{"_id":"54aa34fb09dc5a54232e44b0","count":1, "employee":{fname:abc,lname:abcl}}],"status_code":"200"}
You can call the model form of .populate() on the result objects from an aggregate operation. But the thing is you are going to need a model to represent the "Result" object returned by your aggregation in order to do so.
There are a couple of steps, best explained with a complete listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var employeeSchema = new Schema({
"fname": String,
"lname": String
})
var availSchema = new Schema({
"is_afternoon_scheduled": Boolean,
"employee_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
}
});
var resultSchema = new Schema({
"_id": {
"type": Schema.Types.ObjectId,
"ref": "Employee"
},
"count": Number
});
var Employee = mongoose.model( "Employee", employeeSchema );
var Availability = mongoose.model( "Availability", availSchema );
var Result = mongoose.model( "Result", resultSchema, null );
mongoose.connect('mongodb://localhost/aggtest');
async.series(
[
function(callback) {
async.each([Employee,Availability],function(model,callback) {
model.remove({},function(err,count) {
console.log( count );
callback(err);
});
},callback);
},
function(callback) {
async.waterfall(
[
function(callback) {
var employee = new Employee({
"fname": "abc",
"lname": "xyz"
});
employee.save(function(err,employee) {
console.log(employee),
callback(err,employee);
});
},
function(employee,callback) {
var avail = new Availability({
"is_afternoon_scheduled": true,
"employee_id": employee
});
avail.save(function(err,avail) {
console.log(avail);
callback(err);
});
}
],
callback
);
},
function(callback) {
Availability.aggregate(
[
{ "$group": {
"_id": "$employee_id",
"count": { "$sum": 1 }
}}
],
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
);
}
],
function(err,result) {
if (err) throw err;
mongoose.disconnect();
}
);
That's the complete example, but taking a closer look at what happens inside the aggregate result is the main point:
function(err,results) {
results = results.map(function(result) {
return new Result( result );
});
Employee.populate(results,{ "path": "_id" },function(err,results) {
console.log(results);
callback(err);
});
}
The first thing to be aware of is that the results returned by .aggregate() are not mongoose documents as they would be in a .find() query. This is because aggregation pipelines typically alter the document in results from what the original schema looked like. Since it is just a raw object, each element is re-cast as a mongoose document for the Result model type defined earlier.
Now in order to .populate() with data from Employee, the model form of this method is called on the array of results in document object form along with the "path" argument to the field to be populated.
The end result fills is the data as it comes from the Employee model it was related to.
[ { _id:
{ _id: 54ab2e3328f21063640cf446,
fname: 'abc',
lname: 'xyz',
__v: 0 },
count: 1 } ]
Different to how you process with find, but it is necessary to "re-cast" and manually call in this way due to how the results are returned.
This is working like applied populate with aggregate using inner query.
var mongoose = require("mongoose");
var empid = mongoose.Types.ObjectId("54a0d4c5bffabd6a179834eb");
Availability.aggregate()
.match( { employee_id : empid} )
.group({_id : "$employee_id",count: { $sum: 1 }})
.exec(function (err, response) {
if (err) console.log(err);
if (response.length) {
var x = 0;
for (var i=0; i< response.length; i++) {
empID = response[i]._id;
if (x === response.length -1 ) {
User.find({_id: empID}, function(err, users){
res.json({"message": "success", "data": users, "status_code": "200"});
});
}
x++;
}
}
}
);

Resources