MongoDB Query SubDocuments by field valeu - node.js

So I have this schema for a Supplier:
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Address = require('./Address.js'),
AddressSchema = mongoose.model('Address').schema,
Product = require('./Product.js'),
ProductSchema = mongoose.model('Product').schema;
// Create a new schema for the reviews collection with all the relevant information for a review
var Schema = mongoose.Schema;
var Supplier = new Schema(
{
name: String,
address: AddressSchema,
location: {
type: {type:String, default: 'Point'},
coordinates: [Number] // [<longitude>, <latitude>]
},
products: [ProductSchema]
}
);
Supplier.index({location: '2dsphere'});
var SupplierModel = mongoose.model('Supplier', Supplier );
// export the review model
module.exports = SupplierModel;
Products in my system have a "verified" field which is a boolean. In one of my routes I would like to query the DB to find all the suppliers which have products which aren't verified such that I can then render those products in the page.
I tried this, but unofrtunatelly it returns all the subdocuments no matter if "verified" is true or false:
exports.admin_accept_product_get = function (req, res) {
Supplier.find({'products.verified' : false}, function(err, docs) {
res.render('admin_accept_product', { user : req.user, suppliers: docs });
});
};
Any help is appreciated
Edit:
The previous query would return the following data:
{
"_id" : ObjectId("5b2b839a2cf8820e304d7413"),
"location" : {
"type" : "Point",
"coordinates" : [
-16.5122377,
28.4028329
]
},
"name" : "Tienda1",
"products" : [
{
"verified" : true,
"_id" : ObjectId("5b2b83d32cf8820e304d7420"),
"title" : "Vodka",
"inStock" : 15,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295784515201529168557789bottle.png",
"typeOfAlcohol" : "vodka"
},
{
"verified" : false,
"_id" : ObjectId("5b2b848f8c59960c44df09cd"),
"title" : "Whisky",
"inStock" : 40,
"typeOfItem" : "alcohol",
"sellingPrice" : 15,
"image" : "public/upload/15295786395491529323314298whisky.png",
"typeOfAlcohol" : "whisky"
}
],
"__v" : 2
}
I would like my query to not return the firt product because "verified == true"

You need to use $elemMatch to find the document and $elemMatch for projection of the data
db.collection.find({
products: {
$elemMatch: {
verified: false
}
}
},
{
products: {
$elemMatch: {
verified: false
}
},
location: 1
})
Output
[
{
"_id": ObjectId("5b2b839a2cf8820e304d7413"),
"products": [
{
"_id": ObjectId("5b2b848f8c59960c44df09cd"),
"image": "public/upload/15295786395491529323314298whisky.png",
"inStock": 40,
"sellingPrice": 15,
"title": "Whisky",
"typeOfAlcohol": "whisky",
"typeOfItem": "alcohol",
"verified": false
}
]
}
]
Check it here

Related

Remove object from nested array of objects

I got this user schema
const UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true,
},
groups: [
{
groupName: {
type: String,
required: true,
},
groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }],
},
],
});
And I want to delete a group from the ‘groups’ array based on given ‘userId’ and ‘groupId’, my attempt (with express and mongoose):
router.delete(
"/:userId/:groupId",
catchAsync(async (req, res) => {
const { userId, groupId } = req.params;
const updatedUser = await User.findByIdAndUpdate(
userId,
{ $pull: { "groups.$._id": groupId } },
{ new: true }
);
res.send({ updatedUser });
})
);
The response of the request: I get an error: “The positional operator did not find the match needed from the query.”
Edit:
After I delete a group I need to remove all the group members in the groupMembers array.
User collection structure example:
{
"_id" : "111",
"email" : "michael#gmail.com",
"username" : "michael098",
"groups" : [
{
"_id" : "222"
"groupName" : "family",
"groupMembers" : [
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
}
]
},
{
"_id" : "555"
"groupName" : "friends",
"groupMembers" : [
{
"_id" : "666"
"name" : "Oliver"
}
]
}
]
}
Inside every group there is group members and I have a collection for the group members that I ref in the UserSchema : groupMembers: [{ type: Schema.Types.ObjectId, ref: "GroupMember" }]
GroupMember collection structure example:
{
{
"_id" : "333"
"name" : "Liam"
},
{
"_id" : "444"
"name" : "Noah"
},
{
"_id" : "666"
"name" : "Oliver"
}
}
For example when I get the params of userId="111" and groupId="222" I will delete the whole 'family' group and the whole group members in the groupMembers array (Liam and Noah) from the GroupMember collection.
GroupMember collection after deleting the group with _id="222":
{
{
"_id" : "666"
"name" : "Oliver"
}
}
Assuming an actual doc might look like this (using strings instead of ObjectId to keep the example tighter):
{_id:1, groups: [ { type: "ID1", ref: "XX" }, { type: "ID2", ref: "G1" }, { type: \
"ID3", ref: "G2" } ] }
then this update will remove the subdoc in the groups array where _id = 1 and type = ID2:
db.foo.update({_id:1},
{ $pull: { groups: { type: "ID2" } }}
);

mongoose pull command to delete product from shopping cart

CartController.prototype.addToCart =(req, res, next) => {
// Using mongoose in-built function "findbyID"
Product.findById({_id : req.params.productId}).then( item =>
{
if (!item) {res.status(400).send({message : "item not found"})}
Cart.findByIdAndUpdate(req.params.userId,
{
total_quantity : 0,
total_price : 0,
final_price : 0,
"$push": {"products": {
// Passing Data
productid : item._id,
MRP : item.offers.MRP,
finalprice : item.offers.finalprice,
discount : item.offers.discount,
organization : item.property.organization,
brand : item.property.brand,
color : item.property.color,
weight : item.property.weight,
category : item.property.category
}
},
userid : req.params.userId
},
{ upsert: true, returnNewDocument : true}
).then(() => {
res.status(200).send({message: "product added to cart"});
}).catch(err => {
res.status(500).send(err);
});
}).catch (err => {
res.status(500).send("item fetch related issue found", err);
});
};
//json output
[
{
"_id": "5e5a58843ed45a235c32ac8c",
"__v": 0,
"createdAt": "2020-03-16T18:04:31.370Z",
"updatedAt": "2020-03-16T18:41:23.068Z",
"userid": "5e5a58843ed45a235c32ac8c",
"inCart": false,
"products": [
{
"category": "Home Decor",
"weight": 300,
"color": "Multicolor",
"MRP": 5000,
"productid": "5e6f4234564e2d1b74ba3383",
"_id": "5e6fbfaf3818775ac010187e",
"quantity": 1,
"brand": "",
"organization": "",
"discount": 20
},
{
"category": "Home Decor",
"weight": 300,
"color": "Multicolor",
"MRP": 5000,
"productid": "5e6b21458f7019378c4981ff",
"_id": "5e6fbff53818775ac0101881",
"quantity": 1,
"brand": "",
"organization": "",
"discount": 20
}
],
"final_price": 0,
"total_price": 0,
"total_quantity": 0
}
]
//cart model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var product = new Schema({
productid : {
type: Schema.Types.ObjectId,
// required: true,
ref: 'Product'
},
MRP : {
type : Number,
// required : true
},
finalPrice : {
type : Number
},
discount : {
type : Number, default : 0
},
organization : {
type : String, default : null
},
brand : {
type : String, default : null
},
color : {
type : String
},
weight : {
type : Number
},
category : {
type : String,
// required :true
},
quantity : {
type : Number,
// required : true,
default : 1
}
});
var cartSchema = new Schema({
total_quantity : {type : Number, required : true, default: 0},
total_price : {type : Number, required : true, default: 0},
final_price : {type : Number, required : true, default: 0},
products : [product],
userid : {
type: Schema.Types.ObjectId,
required: true,
ref: 'Pujari'
},
inCart : {type : Boolean, default : 0}
},
{
timestamps:true
}
);
module.exports = mongoose.model('Cart', cartSchema);
//delete cart item
CartController.prototype.deleteCartItem = (req, res, next) => {
Cart.update({userid : req.params.userId}, { $pull: { products : {productid: req.params.productId }}}, {multi: true})
};
So, basically I have to collection one for product and another for cart, my question is how to delete particular product from cart like in bellow json I have to product. I just want to delete object where "productid": "5e6b21458f7019378c4981ff", I tried mongodb $pull operator but its not working for this data.
finally I found the solution, there was a small mistake I was doing
//delete cart item
CartController.prototype.deleteCartItem = (req, res, next) => {
Cart.update({userid : req.params.userId}, { $pull: { products : {category: "Home" }}}, function (err, data) {
if (err) res.send(err)
else res.send(data)
})
For me your update is working (for the given document):
db.collection.updateMany(
{ userid: "5e5a58843ed45a235c32ac8c" },
{ $pull: { "products": { productid: "5e6b21458f7019378c4981ff" } } }
)
{
"acknowledged" : true,
"matchedCount" : 1.0,
"modifiedCount" : 1.0
}

Mongoose update value in Array of Array in NodeJS

my Test Schema:
var TestSchema = new Schema({
testName: String,
topic: {
topicTitle: String,
topicQuestion: [
{
questionTitle: String,
choice: [
{
name: String
age: Number
}
]
}
]
}
}, { collection: 'test' });
var Test = mongoose.model('test', TestSchema);
I want to update one age ($inc)value which I have the choice id.
I can have test id, topicQuestion id and choice id.
How to write this query in mongoose in NodeJS?
Normally I use the below query to update a value:
Test.findOneAndUpdate({ _id: testId }, { $inc: { ... } }, function (err, response) {
...
});
but it is so difficult to get in array and one more array. Thanks
You can use the $[] positional operator to update nested arrays.
router.put("/tests/:testId/:topicQuestionId/:choiceId", async (req, res) => {
const { testId, topicQuestionId, choiceId } = req.params;
const result = await Test.findByIdAndUpdate(
testId,
{
$inc: {
"topic.topicQuestion.$[i].choice.$[j].age": 1
}
},
{
arrayFilters: [{ "i._id": topicQuestionId }, { "j._id": choiceId }],
new: true
}
);
res.send(result);
});
Let's say we have this existing document:
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2116"),
"testName" : "Test 1",
"topic" : {
"topicTitle" : "Title",
"topicQuestion" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211a"),
"questionTitle" : "Question 1 Title",
"choice" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211c"),
"name" : "A",
"age" : 1
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211b"),
"name" : "B",
"age" : 2
}
]
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2117"),
"questionTitle" : "Question 2 Title",
"choice" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2119"),
"name" : "C",
"age" : 3
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2118"),
"name" : "D",
"age" : 4
}
]
}
]
},
"__v" : 0
}
If we want to increment age value of a given choice, we send a PUT request using endpoint like this http://.../tests/5e53e7d9bf65ac4f5cbf2116/5e53e7d9bf65ac4f5cbf211a/5e53e7d9bf65ac4f5cbf211b where
"testId": "5e53e7d9bf65ac4f5cbf2116"
"topicQuestionId": "5e53e7d9bf65ac4f5cbf211a"
"choiceId": "5e53e7d9bf65ac4f5cbf211b"
You need to inform what choice you want and, on the update section, you need change the way you do increment.
Example:
Test.findOneAndUpdate({ _id: testId, topicQuestion.choice._id: choiceId}, { 'topicQuestion.$.choice': {$inc: { age: <numberToIncrement> }}}, {new: true}, function (err, response) {
...
});

Why Mongoose createdAt / updatedAt output a wrong time(only return current time not the time in db)

The Data stored in my db is:
{
"_id" : ObjectId("58da135cfc80bc44f7653fd4"),
"updatedAt" : ISODate("2017-03-28T08:00:59.541Z"),
"createdAt" : ISODate("2017-03-28T07:40:12.742Z"),
"name" : "hello",
"delete" : false,
"enabledPlugins" : [
ObjectId("58c24f65b363502f907738f9")
],
"__v" : 0
}
My Schema Like:
const mongoose = require('./db');
const { Schema } = mongoose;
const templateSchema = new Schema({
name: { type: String, index: true, unique: true },
enabledPlugins: [
{ type: Schema.Types.ObjectId }
],
delete: { type: Boolean, default: false }
}, {
timestamps: true
});
const Template = mongoose.model('Template', templateSchema);
module.exports = Template;
But When I want to get templates, I get the wrong timestamp:
exports.getAllTemplates = async function() {
return await Template.aggregate(
{ $match: { delete: false } },
{ $project: { id: '$_id', _id: 0, name: 1, enabledPlugins: 1, createdAt: 1 } }
);
};
The result like :
[
{
"createdAt": "2017-03-28T17:04:30.502+08:00",
"name": "hello",
"enabledPlugins": [
"58c24f65b363502f907738f9"
],
"id": "58da135cfc80bc44f7653fd4"
}
]
And I found before toJSON, the output has been wrong. I don't use any plugins. All the date type has the same problem.
Thanks, the problem is that I rewrite Date.prototype.toISOString

Mongoose - accessing nested object with .populate

Schema Definitions
Team.js
var TeamSchema = new Schema({
// Team Name.
name: String,
lead: String,
students :type: [{
block : Number,
status : String,
student : {
type: Schema.ObjectId,
ref: 'Student'
}]
});
Student.js
var StudentSchema = new Schema({
name: String,
rollNo : Number,
class : Number
});
How I can populate "student" to get output, as below:
team
{
"__v": 1,
"_id": "5252875356f64d6d28000001",
"students": [
{
"__v": 1,
"_id": "5252875a56f64d6d28000002",
block : 1,
status : joined,
"student": {
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{
"__v": 1,
"_id": "5252875a56f64d6d28000003",
block : 1,
status : joined,
"student": {
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
],
"lead": "Ratha",
}
This is JS I use to get the document using Mongoose:
Team.findOne({
_id: req.team._id
})
.populate('students')
.select('students')
.exec(function(err, team) {
console.log(team);
var options = {
path: 'students.student',
model: 'Student'
};
Student.populate(team.students,options,function(err, students) {
console.log(students);
if (err) {
console.log(students);
res.send(500, {
message: 'Unable to query the team!'
});
} else {
res.send(200, students);
}
});
});
In my console output I get the following:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2249 },
{ block : 1
status: 'joined'
_id: 53aa436b58f760900b3f2250 }]
}
And the expected output is:
{ _id: 53aa434858f760900b3f2246,
students
[ { block : 1
status: 'joined'
student :{
"name": Sumeeth
"rollNo" : 2
"class" : 5
}
},
{ block : 1
status: 'joined'
student :{
"name": Sabari
"rollNo" : 3
"class" : 4
}
}
]
}
Some one please help me where I am wrong. How should I make use of .populate, so that , I can get the entire student object and not only its id.
Reference :
Populate nested array in mongoose
I have been facing same issue. I have use this code for my rescue :
Team.findOne({_id: req.team._id})
.populate({ path: "students.student"})
.exec(function(err, team) {
console.log(team);
});
Here is a simplified version of what you want.
Basic data to set up, first the "students":
{
"_id" : ObjectId("53aa90c83ad07196636e175f"),
"name" : "Bill",
"rollNo" : 1,
"class" : 12
},
{
"_id" : ObjectId("53aa90e93ad07196636e1761"),
"name" : "Ted",
"rollNo" : 2,
"class" : 12
}
And then the "teams" collection:
{
"_id" : ObjectId("53aa91b63ad07196636e1762"),
"name" : "team1",
"lead" : "me",
"students" : [
{
"block" : 1,
"status" : "Y",
"student" : ObjectId("53aa90c83ad07196636e175f")
},
{
"block" : 2,
"status" : "N",
"student" : ObjectId("53aa90e93ad07196636e1761")
}
]
}
This is how you do it:
var async = require('async'),
mongoose = require('mongoose');
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/team');
var teamSchema = new Schema({
name: String,
lead: String,
students: [{
block: Number,
status: String,
student: {
type: Schema.ObjectId, ref: 'Student'
}
}]
});
var studentSchema = new Schema({
name: String,
rollNo: Number,
class: Number
});
var Team = mongoose.model( "Team", teamSchema );
var Student = mongoose.model( "Student", studentSchema );
Team.findById("53aa91b63ad07196636e1762")
.select('students')
.exec(function(err, team) {
console.log( team );
async.forEach(team.students, function(student,callback) {
Student.populate(
student,
{ "path": "student" },
function(err,output) {
if (err) throw err;
callback();
}
);
},function(err) {
console.log( JSON.stringify( team, undefined, 4 ) );
});
});
And it gives you the results:
{
"_id": "53aa91b63ad07196636e1762",
"students": [
{
"block": 1,
"status": "Y",
"student": {
"_id": "53aa90c83ad07196636e175f",
"name": "Bill",
"rollNo": 1,
"class": 12
}
},
{
"block": 2,
"status": "N",
"student": {
"_id": "53aa90e93ad07196636e1761",
"name": "Ted",
"rollNo": 2,
"class": 12
}
}
]
}
You really do not need the "async" module, but I am just "in the habit" as it were. It doesn't "block" so therefore I consider it better.
So as you can see, you initial .populate() call does not do anything as it expects to "key" off of an _id value in the foreign collection from an array input which this "strictly speaking" is not so as the "key" is on "student" containing the "foreign key".
I really did cover this in a recent answer here, maybe not exactly specific to your situation. It seems that your search did not turn up the correct "same answer" ( though not exactly ) for you to draw reference from.
You are overthinking it. Let Mongoose do the work for you.
Team.findOne({
_id: req.team._id
})
.populate({path:'students'})
.exec(function(err, team) {
console.log(team);
});
This will return students as documents rather than just the ids.
TL DR
const team = await Team.findById(req.team._id)
.populate("students");
team.students = await Student.populate(team.students, {path: "student"});
Context
Reading from all the answers I went testing everything and just Neil Lun's answer worked for me. The problem is it was on the path to a cb hell. So I cracked my head a little and 'refactored' to an elegant one-liner.
const foundPost = await Post.findById(req.params.id)
.populate("comments")
.populate("author");
foundPost.comments = await User.populate(foundPost.comments, {path: "author"});
My initial problem:
{
title: "Hello World",
description: "lorem",
author: {/* populated */},
comments: [ // populated
{text: "hi", author: {/* not populated */ }}
]
};
How my models basically are:
User = {
author,
password
};
Post = {
title,
description,
author: {}, //ref User
comments: [] // ref Comment
};
Comment = {
text,
author: {} // ref User
};
The output after problem solved:
{
comments: [
{
_id: "5dfe3dada7f3570b60dd977f",
text: "hi",
author: {_id: "5df2f84d4d9fcb228cd1df42", username: "jo", password: "123"}
}
],
_id: "5da3cfff50cf094c68aa2a37",
title: "Hello World",
description: "lorem",
author: {
_id: "5df2f84d4d9fcb228cd1aef6",
username: "la",
password: "abc"
}
};

Resources