Populate a Nested Mongoose Object w/ a Nested Object - node.js

Working on my first project and have been stumped on this for a couple days.
I'm trying to populate an object that contains the brewery info and the single corresponding beer from the Beer model.
models.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var beerSchema = Schema({
breweryName: String,
beer: [{
beerName: String
}]
});
var draftlistSchema = Schema ({
userName: String,
tap: [{
tapNo: Number,
_tapBeer: { type: Schema.Types.ObjectId, ref: 'Beer' },
tapStatus: String
}]
});
var Draftlist = mongoose.model('Draftlist', draftlistSchema);
var Beer = mongoose.model('Beer', beerSchema);
module.exports = {
Draftlist: Draftlist,
Beer: Beer
}
route.js
var getDraftlist = function(user, callback) {
models.Draftlist.findOne({ 'userName': user }).populate( {
path: 'tap._tapBeer',
model: 'Draftlist'
}).exec(function(err, draftlist) {
// console.log(draftlist.tap)
console.log(draftlist);
callback(draftlist);
});
};`
I'm getting a null returned with the current code. Ideally I would like the returned object to look something like--
{
breweryName: Some Brewery,
beer: {// Beer object that was referenced by Id //}
}
Draftlist Object w/ null return
{ _id: 590bd0615a190204fca6d467,
userName: 'A New User1',
__v: 1,
tap:
[ { tapNo: 1,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46d },
{ tapNo: 2,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46c },
{ tapNo: 3,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46b },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d46a },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d469 },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d468 },
{ _tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0735a190204fca6d470 } ] }
Beer Object
{ breweryName: 'Beachwood',
_id: 590bd3f1ed13510514405cba,
beer: [ { beerName: 'IPA', _id: 590bd3f1ed13510514405cbb } ] }

Use mongoose-deep-populate
const
mongoose = require('mongoose'),
deepPopulate = require('mongoose-deep-populate')(mongoose);
Schema = mongoose.Schema;
const
BeerSchema = Schema({
breweryName: Schema.Types.String,
beer: [{
beerName: Schema.Types.String
}]
}),
DraftlistSchema = Schema ({
userName: Schema.Types.String,
tap: [{
tapNo: Schema.Types.Number,
_tapBeer: {
type: Schema.Types.ObjectId,
ref: 'Beer'
},
tapStatus: Schema.Types.String
}]
});
DraftlistSchema.plugin(deepPopulate);
var Draftlist = mongoose.model('Draftlist', DraftlistSchema);
var Beer = mongoose.model('Beer', BeerSchema);
module.exports = {Draftlist, Beer};
usage:
const getDraftlist = (userName, callback) => {
models
.Draftlist
.findOne({
userName
})
.deepPopulate('tap._tapBeer')
.exec((err, draftlist) => {
console.log(draftlist);
callback(draftlist);
});
};
or:
const Draftlist = models.Draftlist;
const getDraftlist = (userName, callback) => {
Draftlist
.findOne({
userName
})
.exec((err, draftlist) => {
Draftlist
.deepPopulate(
draftlist, 'tap._tapBeer',
(err, draflist) => {
console.log(draftlist);
callback(draftlist);
});
});
};

Related

Mongoose Could not find path "${filterPath}" in schema by updating a subdocument

In this Items object:
{
"items": [
{
"_id": "63a48f12a9731cfd8a64b0b1",
"item_name": "addidas shoes",
"__v": 0,
"rating": [
{
"_id": "63a48fd51fb70775d216eb87",
"rate": 1,
"user_id": "6398a1a157d6146413b23b43"
}
]
}
]
}
I'm trying to update the rating property if a user_id inside of it already exists, else, add a new object into it.
const addRating = async (req, res) => {
const { rate, user_id, item_id } = req.body;
// item_id = 63a48f12a9731cfd8a64b0b1 user_id = 6398a1a157d6146413b23b43 rate = 6
// Adding ratings to the selected item
const test = await itemDB.item.updateOne(
{ _id: item_id, rating: { user_id: user_id } },
{ $push: { "items.rating.$[i].rate": rate } },
{ arrayFilters: [{ "i.user_id": user_id }], upsert: true }
);
console.log(test);
res.json({ message: "success" });
};
I wanted to change something in the rating property so I set the filter as above but it gives me this error when hitting the endpoint:
\node_modules\mongoose\lib\helpers\update\castArrayFilters.js:74
throw new Error(`Could not find path "${filterPath}" in schema`);
^
Error: Could not find path "items.rating.0.user_id" in schema
This is my Items Schema:
const mongoose = require("mongoose");
const RateSchema = mongoose.Schema({
rate: {
type: Number,
required: true,
},
user_id: {
type: mongoose.ObjectId,
},
item_id: {
type: mongoose.ObjectId,
},
});
const ItemSchema = mongoose.Schema({
item_name: {
type: String,
required: true,
},
rating: {
type: [RateSchema],
},
});
module.exports = mongoose.model("Items", ItemSchema);
It looks like it is not noticing that items is also an array when applying the array filter to rating.
Try using the all-positional operator like:
{ $push: { "items.$[].rating.$[i].rate": rate } }

autopopulate & virtual in child schema does not work

I have the following schemas:
"use strict";
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
autopopulate = require('mongoose-autopopulate');
const child = new Schema({
userUuid: {
type: String,
required: true
},
timeStamp: {
type: Date,
default: new Date()
}
}, {toJSON: {virtuals: true}});
child.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
const parentList= new Schema({
//some properties
children: [child]
});
parentList.plugin(autopopulate);
module.exports = parentList;
I need the Children's list to extract a full object - but it does not work.
When I put a single user not as a child then it works well:
const try= new Schema({
//some properties
userUuid: {
type: String,
required: true
}
}, {toJSON: {virtuals: true}});
try.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
try.plugin(autopopulate);
module.exports = try;
This leads me to the conclusion that the problem is when the virtual is within the child schema
What am I missing?
Here's the full code of my attempt to reproduce yours :
const
{ randomUUID } = require('node:crypto'),
{ MongoMemoryServer } = require('mongodb-memory-server'),
mongoose = require('mongoose'),
{ Schema } = mongoose,
autopopulate = require('mongoose-autopopulate');
(async () => {
const
dbServer = await MongoMemoryServer.create(),
dbClient = mongoose.createConnection(dbServer.getUri());
dbClient.on('disconnected', () => dbServer.stop());
await new Promise(resolve => dbClient.once('connected', () => resolve()));
try {
const trySchema = new Schema({
//some properties
userUuid: {
type: String,
required: true
}
}, { toJSON: { virtuals: true } });
trySchema.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
trySchema.plugin(autopopulate);
const childSchema = new Schema({
userUuid: {
type: String,
required: true
},
timeStamp: {
type: Date,
default: new Date()
}
}, { toJSON: { virtuals: true } });
childSchema.virtual('user', {
ref: 'users',
localField: 'userUuid',
foreignField: 'uuid',
autopopulate: true
});
childSchema.plugin(autopopulate);
const parentListSchema = new Schema({
//some properties
children: [childSchema]
});
parentListSchema.plugin(autopopulate);
const userSchema = new Schema({
uuid: {
type: String,
required: true
}
});
const
Try = dbClient.model('try', trySchema),
Child = dbClient.model('child', childSchema),
ParentList = dbClient.model('parentList', parentListSchema),
User = dbClient.model('users', userSchema);
const userUuid = randomUUID();
await new User({ uuid: userUuid }).save();
await new Try({ userUuid }).save();
const child = await new Child({ userUuid }).save();
await new ParentList({ children: [child] }).save();
console.log('User:', (await User.findOne().exec()).toJSON());
console.log('Try:', (await Try.findOne().exec()).toJSON());
console.log('Child:', (await Child.findOne().exec()).toJSON());
console.log('ParentList:', (await ParentList.findOne().exec()).toJSON());
}
catch(error){
console.error(error);
}
dbClient.close();
})();
Which outputs :
User: {
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
Try: {
_id: new ObjectId("62c6e7bcef50638fe0097868"),
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0,
user: [
{
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
],
id: '62c6e7bcef50638fe0097868'
}
Child: {
_id: new ObjectId("62c6e7bcef50638fe009786b"),
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
timeStamp: 2022-07-07T14:03:40.902Z,
__v: 0,
user: [
{
_id: new ObjectId("62c6e7bcef50638fe0097866"),
uuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
__v: 0
}
],
id: '62c6e7bcef50638fe009786b'
}
ParentList: {
_id: new ObjectId("62c6e7bcef50638fe009786e"),
children: [
{
userUuid: 'bb5af665-759a-4da0-880d-8a54ce42be4c',
timeStamp: 2022-07-07T14:03:40.902Z,
_id: new ObjectId("62c6e7bcef50638fe009786b"),
__v: 0,
id: '62c6e7bcef50638fe009786b'
}
],
__v: 0
}
I'm not sure what you meant about [needing] the Children's list to extract a full object.
If you were talking about Child.user then you were only missing child.plugin(autopopulate);.
If you were talking about ParentList.children then it worked out of the box for me.

MongoDB $push is not actually pushing anything onto the array

I commented the line of code with // This Command Does not work where I suspect that it is breaking. In the debug log of mongoose, the output looks like this: But nothing is added to the medicineIds array in the Monday object for the DaysOfWeek schema.
The following is the debug output for DayOfWeek.findOneAndUpdate() where I push back onto the array, and am not seeing the result in my mongo database.
Mongoose: dayofweeks.insertOne({ medicineIds: [], _id: 'Monday', __v: 0 }, { session: null }) // <- response to $push
Mongoose: medicines.insertOne({ times: [ 1, 2 ], dayNames: [ 'Monday' ], _id: ObjectId("5e73d816d54b1202e15bb96b"), nam
e: 'Provolone', count: 23, __v: 0 }, { session: null })
Mongoose: dayofweeks.findOne({ _id: 'Monday' }, { projection: {} })
Mutation
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
addDayOfWeek: {
type: DayOfWeekType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(parent, args) {
let dayOfWeek = new DayOfWeek({
_id: args.name,
medicineIds: new Array()
});
return dayOfWeek.save();
}
},
addNewMedicine: {
type: MedicineType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) },
count: { type: new GraphQLNonNull(GraphQLInt) },
times: { type: new GraphQLNonNull(GraphQLList(GraphQLInt))},
dayNames: { type: new GraphQLNonNull(GraphQLList(GraphQLString))}
},
resolve (parent, args) {
let medicine = new Medicine({
name: args.name,
count: args.count,
times: args.times,
dayNames: args.dayNames
});
args.dayNames.forEach((dayId) => {
DayOfWeek.findOneAndUpdate( // This Command Does Not Work:
// medicine._id, dayId are correct at this point of the
//code
{ _id: dayId },
{ $push: { medicineIds: medicine._id }},
{ new: true, useFindAndModify: false }
);
});
return medicine.save();
}
}
}
});
DayOfWeek Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const dayOfWeekSchema = new Schema({
_id: String,
medicineIds: [String] // I am trying to push onto this array
});
module.exports = mongoose.model('DayOfWeek', dayOfWeekSchema);
Medicine Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const medicineSchema = new Schema({
id: String,
count: Number,
name: String,
times: [Number],
dayNames: [String]
});
module.exports = mongoose.model('Medicine', medicineSchema);
await Promise.all(args.dayNames.map(dayName => {
return DayOfWeek.findOneAndUpdate({ _id: dayName }, { $push: { medicineIds: medicine._id }});
})).catch((err) => console.error(err));
return await medicine.save();
I just did that and it works. hmm.

Node.js - Angular - MongoDB - for `userid` , get `username`

I have such mongoose.Schema:
User.js:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
password: String,
email: String,
});
module.exports = mongoose.model('users', UserSchema, 'users');
product.js:
const mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate');
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
userid: {type:String, required: true},
product_name: {type:String, required: true}
});
ProductSchema.plugin(mongoosePaginate);
module.exports = mongoose.model('products', ProductSchema, 'products');
method:
exports.selectUsersProductsCount = function (req, res, next) {
Product.aggregate([
{"$group" : {_id:{userid:"$userid" }, count:{$sum:1} } }
],
function(err, result) {
if(err){ res.status(400).json({ success: false, message:'Error processing request '+ err }); }
res.status(201).json({
success: true,
data: result
});
console.log(result);
})
}
result:
[ { _id: { userid: '5ab655bbb94733156c438112' }, count: 3 },
{ _id: { userid: '5ab425c6f5bff145304092f7' }, count: 1 } ]
I want the username field to be displayed as well. I do not know how to use join. Counts correctly, but does not display the username field.
How to correct the aggregate method to display:
userid, username, count
[ { _id: { userid: '5ab655bbb94733156c438112', username: 'Jon Alon' }, count: 3 },
{ _id: { userid: '5ab425c6f5bff145304092f7', username: 'Jonson Con' }, count: 1 } ]
examples data:
Product
{
_id:O bjectId(5ab7da972ade533790268f47),
userid:"5ab655bbb94733156c438112",
product_name:"gs",
__v:0
},{
_id: ObjectId(5ab7daa92ade533790268f48),
userid:"5ab655bbb94733156c438112",
product_name:"dg",
__v:0
}
User
{
_id: ObjectId(5ab655bbb94733156c438112),
username: "rrrr",
email:"rrrr",
__v:0
}
You have to use the $lookup function to achieve that but you need to have some property in common between the 2 collections.
You would need to add userId on UserSchema or some other property so you could "join"
In your example u could try
db.product.aggregate([ {$lookup:{ from: "user", localField: "ObjectId(userid)", foreignField: "ObjectId(_id)", as: "username" }}, {"$group" : {_id:{userid:"$userid", username : "$username" }, count:{$sum:1} } } ])
solution
products.aggregate([
{$lookup:{
from: "users",
localField: "ObjectId(userid)",
foreignField: "ObjectId(_id)",
as: "users"
}},
{ "$group" :
{
_id: { userid: "$userid" },
name: { $last: "$users.username" } ,
count: { $sum: 1 }
}
}, {$sort: {"_id.userid": 1}}
], function(err, result) {
console.log(result);
if(err){ res.status(400).json({ success: false, message:'Error processing request '+ err }); }
res.status(201).json({
success: true,
data: result
});
}
);
}
returns:
[ { _id: { userid: '5ab425c6f5bff145304092f7' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 3 },
{ _id: { userid: '5ab655bbb94733156c438112' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 1 },
{ _id: { userid: '5aba7e8c045115340496becd' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 2 } ]

How to update a document having an array of arrays in MongoDB with Mongoose?

Given the following schema:
const item = {
_id: false,
amount: { type: Number, required: true },
};
const item_schema = new Schema({ item_history: [item] });
const parent_schema = new Schema({
...
items: [item_schema],
...
})
and this document in the database
{
...
items: [{ _id: 1, item_history: [{ amount: 10 }] }]
...
}
Let's say I want to update this document with these items:
const changed_or_new_items = [{ _id: 1, amount: 20 }, { amount: 30 }];
Which should result in this object in the database:
{
...
items: [{ _id: 1, item_history: [{ amount: 10 }, { amount: 20}] },
{ _id: 2, item_history: [{ amount: 30 }] }]
...
}
This is how I currently update the document:
const parent = await Parent.findOne(some_query).exec();
changed_or_new_items.forEach(item => {
if (!item._id) {
parent.items.push({ item_history: [item] });
}
else {
const item_doc = parent.items.id(item._id);
item_doc.item_history.push(_.omit(item, '_id'));
}
});
await parent.save();
Would the above be possible to achieve using an update operation e.g. findOneAndUpdate and if so, how?
You can use findOneAndUpdate with arrayFilters as:
Parent.findOneAndUpdate(
{ 'items._id': 1 },
{ '$set': { 'items.$.item_history.$[element].amount': 30 } },
{
'arrayFilters': [ {'element.amount': 20} ],
'new': true,
'upsert': true
}, (err, updatedParent ) => {
if (err) res.status(400).json(err);
res.status(200).json(updatedParent);
}
);

Resources