populate with condition in mongoose - node.js

I have a movie booking data like below
movie order schema
{
"movie_id": "5d64fb7975214a183bf10f5b",
"variant_id": "5d64fda8fc7f911a77afd55c",
}
and movie schema data like below
{
"_id":"5d64fb7975214a183bf10f5b",
"description":"Sahoo movie ",
"options":[
],
"variants":[
{
"enabled":true,
"_id":"5d64fda8fc7f911a77afd55c",
"variant_name":"",
"regular_price":345,
"sale_price":125,
"stock_quantity":45,
},
{
"enabled":true,
"_id":"5d661c8181a4572a27f048dd",
"variant_name":"",
"regular_price":120,
"sale_price":50,
"stock_quantity":10,
}
],
"on_sale":false,
"variable":true
}
now I'm trying to querying the movie order
let data = await MovieOrder.find().populate(movie_id)
but it is giving movie details with all the variant.but what I'm looking for here is
In the movie order what is the variant_id is present based on that I need to populate the movie with variant based on that variant id on the movie order
Note: the result should be, what are the variant_id in the movie order schema is equal to variant id in the movie schema
Hope you guys understand my problem, please give me the solution

With the way your schema is designed it is hard for populate to filter the movies variants array with the variant_id in the movies order
as this is not how populate works.
In order to use populate properly, you would have to change the movies schema making the variants array as ref to
Variants model. For example, your schema definitions would need to look like
Schema definitions
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const variantSchema = new Schema({
enabled: Boolean,
variant_name: String,
regular_price: Number,
sale_price: Number,
stock_quantity: Number
})
const Variant = mongoose.model('Variant', variantSchema)
const movieSchema = new Schema({
description: String,
options: [String],
variants: [{ type: 'ObjectId', ref: 'Variant' }],
on_sale: Boolean,
variable: Boolean
})
const Movie = mongoose.model('Movie', movieSchema)
await MovieOrder.find().populate('movie_id variant_id')
This way your query just returns the movie and the variant it needs.
However, if the current schema design remains as it is, you can use $lookup in an aggregate pipeline to do the populate and then filter the resulting array using $filter on the variant that matches the variant_id field in your MovieOrder model:
await MovieOrder.aggregate([
{ '$lookup': {
'from': 'movies',
'localField': "movie_id", // field in the movieorders collection
'foreignField': "_id", // field in the movies collection
'as': "movie"
} },
{ '$addFields': {
'movie': { '$arrayElemeAt': ['$movie', 0 ] }
} },
{ '$addFields': {
'movie.variants': {
'$filter': {
'input': '$movie.variants',
'cond': { '$eq': ['$variant_id', '$$this._id'] }
}
}
} },
]).exec()

Related

use a variable value in lookup mongoose expression

I am using a mongoose model that brings documents and I add an aggregate to "join" with other collection. The real problem is when I use aggregate I cannot filter documents in the "original" collection (similar to findOne()). So when I use the aggregate function I get all documents but I just need 1.
i.e. (this is only an example for a point describe)
Collection A = {id,name,birthdate,dni}
Collection B = {avatar,dni.....}
a.aggregate([ '$lookup': {
'from': 'avatars',
'localField': 'dni',
'foreignField': 'dni',
'as': 'aditionalinfo'
} ])
this result in a full collection A + additionalinfo object from B for each document but supposed to bring one person from collection A with the additional data (additionalinfo). I mean, I need to bring one document "joined" with the corresponding document from the other collection.
here is the last code I tried
let ley = await leyes.aggregate([
{
'$lookup': {
'from': 'digessituacion',
'localField': 'idsituacio',
'foreignField': 'idsituacio',
'as': 'situacion'
}
}, {
'$lookup': {
'from': 'digescategoria',
'localField': 'catego',
'foreignField': 'catego',
'as': 'categoria'
}
},
{
'$match': {
'ley': 8996 // this is the number which I need to set variable cause is passed from params
}
}]);
this is the schema
import { Schema, model } from "mongoose";
const leyesSchema = new Schema(
{
ley: Number,
resumen: String,
notas: String,
txtley: String,
catego: String,
detcatego: String,
idsituacio: Number,
promulgada: Date,
sancionada: Date,
},
{ timestamps: false, versionKey: false }
);
export default model("leyes", leyesSchema);
and the Schema of collection needed to "join"
import { Schema, model } from "mongoose";
const digessituacionSchema = new Schema(
{
idsituacio: Number,
situacion: String,
},
{ timestamps: false, versionKey: false }
);
export default model("digessituacion", digessituacionSchema);
if I put the number manually works but is not usable cause it must be a variable. Anyone knows the way for do this? It is possible?
When the req.params is parsed, they are always parsed as type string. Since your ley field is of type number, you have to parse it first:
"$match": {
"ley": parseInt(req.params.ley, 10)
}

How to get categories and sub-categories in single API response

I have two collections Categories and Subcategories inside a Categories collection. I have an array that is storing the ids of subcategories that are stored in Subcategories collection. Below is my document structure:
Categories collection
{
id:65,
title:"Automotive",
active:true,
subcategories:[35,28,30]
}
subcategories collection
{
id:35,
title:"Automotive technology",
category_id:65,
active:true
},
{
id:28,
title:"Automotive coatings",
category_id:65,
active:true
},
{
id:30,
title:"Machinery and equipments",
category_id:65,
active:true
}
As seen in above collection 3 documents from subcategories collection have been associated with the category document. I want to fetch data in below format on single API hit.
API response should be in below format:
{
data:{
category:{
id:65,
title:"Automotive",
subcategories:[{
id:35,
name:"Automotive technology",
},
{
id:28,
name:"Automotive coatings",
},
{
id:30,
name:"Machinery and equipments",
}]
},
category:{
id:66,
title:"Food",
subcategories:[{
id:23,
name:"Drinks",
},
{
id:2,
name:"Additives",
}]
},
},
messsage:"Success",
code:200
}
As of now I am able to get data in 2 api hits that is like first getting all the categories
const category = await db.categories.find({});
Then on click of some particular category fetching all the sub categories based on the category id.
const subCategories = await db.SubCategories.find({category_id:id});
How can I get the above data in desired format in single API hit?
You need something like this, also, if you use mongoose, you can use .populate()
To format data you can use $project stage in aggregation pipeline or projection in .find()
If you want to use Mongoose with populate:
CategorySchema:
const CategorySchema= new mongoose.Schema({
...
subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'SubCategory' }],
...
});
need _id column on reference table
ref content is must be equal to model name like
module.exports = mongoose.model('SubCategory', SubCategorySchema);
Controller:
const categories = await Category.find({})
.populate({
path: 'subCategories'
})
path content is must be equal to column name
If you want to write with Mongo query:
db.getCollection("categories").aggregate([
{
$lookup:
{
from: 'subCategories',
localField: 'subCategories',
foreignField: 'id',
as: 'subCategories'
}
}
])
You get empty object because you are not using "_id" field for join schemas. If you want to use "id" field u need to use virtuals on Mongoose:
https://mongoosejs.com/docs/tutorials/virtuals.html#populate

MongoDB - update data in array of objects within object

I have a document in mongoDB structured like that
_id: ObjectId("generatedByMongo"),
name: {
required: true,
type: String,
trim: true
},
last: {
required: true,
type: String,
trim: true
},
grades: [{
grade: {
_id: ObjectId(""),
grade: Number,
date: date
}
}]
And to server I send array of objects containing 3 fields
[
{studentId}, {gradeId}, {newGrade}
]
What I'm trying to accomplish is I want to find in within that user collection grade with given gradeId and update it's value to newGrade. As far as I tried to do that I have done this
router.patch('/students/updateGrade',async(req,res) => {
const studentId = req.body.updateGradeArray[0].studentId;
const gradeId = req.body.updateGradeArray[0].gradeId;
const newGrade = req.body.updateGradeArray[0].newGrade;
try {
const student = await Student.find({_id: studentId})
.select({'grades': {$elemMatch: {_id: gradeId}}});
} catch(e) {
console.log(e);
}
}
);
If you intend to update just grade.grade(the number value), try this:
Student.updateOne(
// Find a document with _id matching the studentId
{ "_id": studentId },
// Update the student grade
{ $set: { "grades.$[selectedGrade].grade": newGrade } },
{ arrayFilters: [{ "selectedGrade._id": gradeId }] },
)
Why this should work:
Since you are trying to update a student document, you should be using one of MongoDB update methods not find. In the query above, I'm using the updateOne method. Inside the updateOne, I am using a combination of $set and $[identifier] update operators to update the student grade.
I hope this helps✌🏾

Can't push items in mongo array

I can't push items into MongoDB array every time that i try to push a new element it creates an empty object and i cant figure out why,
I already used the
Collection.Array.push({element})&
Collection.save()
but i cant figure out a solution
This is My Schema
const Schema = mongoose.Schema;
var ParticipantSchema = new Schema({
nom:{Type:String},
prenom:{Type:String},
email:{Type:String}
})
var CompetitionSchema = new Schema({
nom:String,
date:Date,
place:String,
participant :[ParticipantSchema]
})
module.exports = mongoose.model("Competition",CompetitionSchema);
This is my funtion
exports.addParticipant=function(req,res){
var newParticipant={
"nom":req.body.nom,
"prenom":req.body.prenom,
"email":req.body.email
}
Competition.updateOne(
{ _id:req.body.id},
{ $push: { participant: newParticipant } },
(err,done)=>{
return res.json(done)
}
);
}
the result is always an empty object like below
{
"_id": "5ded0eeb85daa100dc5e57bf",
"nom": "Final",
"date": "2019-01-01T23:00:00.000Z",
"place": "Sousse",
"participant": [
{
"_id": "5ded0eeb85daa100dc5e57c0"
},
{
"_id": "5dee3c1b08474e27ac70672e"
}
],
"__v": 0
}
There is no problem in your code, the only problem is that in schema definition you have Type, but it must be type.
If you update your ParticipantSchema like this, it will work:
var ParticipantSchema = new Schema({
nom: { type: String },
prenom: { type: String },
email: { type: String }
});
You are using another Schema in the Array. This results in so-called subdocuments (https://mongoosejs.com/docs/subdocs.html). Mongoose does not populate subdocuments by default. So all you see is just the _id. You can use the populate method to see all subdocuments in detail. ( https://mongoosejs.com/docs/populate.html ) .
Example :
Competition.
find({}).
populate('participant').
exec(function (err, comps) {
//
});
You can either use populate on the Model or on the Document. For populating a document, take a look at https://mongoosejs.com/docs/api.html#document_Document-populate . There is also a auto-populate plugin available via npm but in most cases it's not necessary : https://www.npmjs.com/package/mongoose-autopopulate .

Mongoose count certain element in an embedded documents array

I am using mongoose 4.6.3.
I have the following schema :
var mongoose = require('mongoose');
var User = require('./User');
var TicketSchema = new mongoose.Schema({
user : { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
},
{
timestamps: true
});
var DrawSchema = new mongoose.Schema({
...
max_ticket_per_user : { type : Number, required: true },
tickets: [TicketSchema]
});
module.exports = mongoose.model('Draw', DrawSchema);
How can I count the embedded documents of a certain User ObjectId(user field in TicketSchema) in a Draw's tickets(tickets field in DrawSchema) ?
I want to count the tickets of a user for a single draw.
Would it be better to change my schema design ?
Thanks
You can use the aggregation framework taking advantage of the $filter and $size operators to get the filtered array with elements that match the user id and its size respectively which will subsequently give you the count.
For an single draw, consider adding a $match pipeline operator as your initial step with the _id query to filter the documents in the collection.
Consider running the following aggregation pipeline to get the desired result:
Draw.aggregate([
{ "$match": { "_id": drawId } },
{
"$project": {
"ticketsCount": {
"$size": {
"$filter": {
"input": "$tickets",
"as": "item",
"cond": { "$eq": [ "$$item.user", userId ] }
}
}
}
}
}
]).exec(function(err, result) {
console.log(result);
});
You can pass the .count() deep parameters like any other query object:
Draw.count({'tickets.user._id' : userId}, console.log);
Make sure the userId variable is an ObjectId. If it's a string, do this:
const ObjectId = require('mongoose').Types.ObjectId;
let userId = new ObjectId(incomingStringId);

Resources