Mongoose Virtual field with async getter - node.js

I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?

What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}

Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});

Related

how to insert into deeply nested element API nodejs mongoose

I have 4 nested documents as follow:
//Nested sub document subControl
const SubControlSchema = new Schema({
subControlNo: {
type: String
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document control
const ControlSubSchema = new Schema({
mainControl: {
type: String
},
subControls: [SubControlSchema],
controlDescription: {
type: String,
trim: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document domain
const DomainSubSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
domainNo: {
type: String,
trim: true
},
domainName: {
type: String,
trim: true
},
domainDescription: {
type: String,
trim: true
},
controls: [ControlSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
// framework Schema
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
regulator: {
type: Schema.Types.ObjectId,
ref: 'Regulator',
default: null
},
client: {
type: Schema.Types.ObjectId,
ref: 'Client',
default: null
},
domains: [DomainSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Framework', FrameworkSchema);
I'm trying to post a control under the domain which is inside the framework, here's what I have been trying to do:
//Add new control under a specific domain and framework
router.post('/add/:frameworkId/:domainId', auth, async (req, res) => {
try {
const control = req.body.controls; //take the request from the body
const query = { _id: req.params.frameworkId, _id: req.params.domainId };//pushing into the framework model by taking the ID from URL
await Framework.updateOne(query, { $push: { domains: control } }).exec(); //push the query into the framework model
res.status(200).json({
success: true,
controls: control
});
} catch (error) {
res.status(400).json({
// error: 'Your request could not be processed. Please try again.'
error
});
}
});
Data posted in postman:
Link: http://localhost:3000/api/framework/add/6233277f411377367f8ad1c0/6233277f411377367f8ad1c1
{
"controls":
{
"mainControl": "1-5",
"subControls": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
}
Response:
{
"success": true,
"controls": {
"mainControl": "1-5",
"subControls": [
{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}
],
"controlDescription": "controlDescriptionTest"
}
}
Problem: I'm not getting any new data in mongodb , any idea if I'm approaching this the correct way? I'm guessing the data is posted correctly and It's a problem with saving it to the database
Picture of my schema: I want to be able to add elements under the controls:
First if you want your code to insert and not update you should use insertOne and not updateOne, regarding an "update" operation I can see 2 potential "issues" here:
req.params.frameworkId and req.params.domainId come as string type. And I assume the _id field is type ObjectId and not string.
To fix this you just need to cast it to the proper type, like so:
import { ObjectId } from 'mongodb';
...
{ _id: new ObjectId(req.params.frameworkId) }
Both parameters are "querying" the same field (_id), unless this is intentional somehow if these values are different it will never find a document to match, this should be changed.
Lastly if you want to update an existing object if exists, and if not insert then you should use updateOne with the upsert option:
await Framework.updateOne(query, { $push: { domains: control } }, { upsert: true }).exec();

Mongo populate not working and not retrieve data model

I have a problem in populate method in mongodb it can't retrieve data from model. Can anyone help me solve that problem?
This is the code
router.get('/', auth, async (req, res) => {
try {
const user = req.user._id;
const wishlist = await Wishlist.find({ user, isLiked: true })
.populate({
path: 'Products',
select: 'title',
})
.sort('-updated');
res.status(200).json({
wishlist,
});
} catch (error) {
res.status(400).json({
error: 'Your request could not be processed. Please try again.',
});
}
});
When I navigate to http://localhost:3000/wishlist/, this is the response I get:
{
"wishlist": [
{
"product": "60cb5eb82cc7091ae2e31c88",
"user": "60cb6c46291247466fe08f92",
"isLiked": true,
"_id": "60d1a656567e08bf89571209",
"updated": "2021-06-22T10:09:25.295Z",
"created": "2021-06-22T08:59:02.434Z",
"__v": 0
}
]
}
The model of products
const mongoose = require('mongoose');
const { Schema } = mongoose;
const ProductSchema = mongoose.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
category:{
type: Schema.Types.ObjectId,
ref: 'Categories',
default: null
},
photo: { type: String, required: true },
createdAt: {
type: Date,
default: new Date(),
},
updateAt: Date,
price: {
type: String,
required: true,
},
quantity: {
type: Number
}
,
isActive: {
type: Boolean,
default: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
}
});
module.exports = mongoose.model('Product', ProductSchema);
The model for wishlist:
const Mongoose = require('mongoose');
const { Schema } = Mongoose;
// Wishlist Schema
const WishlistSchema = new Schema({
product: {
type: Schema.Types.ObjectId,
ref: 'Product',
default: null,
},
user: {
type: Schema.Types.ObjectId,
ref: 'User',
default: null,
},
isLiked: {
type: Boolean,
},
updated: {
type: Date,
default: Date.now,
},
created: {
type: Date,
default: Date.now,
},
});
module.exports = Mongoose.model('Wishlist', WishlistSchema);
Can anyone help me please to find the solution for that problem?

Mongoose Trying to populate

I have some issues
Cant populate CartProduct, just show the ObjectId.
There is way to make every time that CartProduct create, add automatically to? cart.
is this the right way of schemas structure?
Cart
const CartSchema = new Schema({
active: { type: Boolean, required: true, default: true },
createAt: { type: Date, default: Date.now },
client: { type: Schema.Types.ObjectId, ref: "User", required: true },
products: [{ type: Schema.Types.ObjectId, ref: "CartProduct" }],
});
const Cart = model("Cart", CartSchema);
Cart Product
const CartProductSchema = new Schema({
item: { type: Schema.Types.ObjectId, ref: "Product", required: true },
cart: { type: Schema.Types.ObjectId, ref: "Cart", required: true },
quantity: { type: Number, required: true },
totalPrice: { type: Number, required: true },
});
const CartProduct = model("CartProduct", CartProductSchema);
Product
const ProductSchema = new Schema({
name: { type: String, required: true },
price: { type: Number, required: true },
image: { type: String, required: true },
category: { type: Schema.Types.ObjectId, ref: "Category", require: true },
});
const Product = model("Product", ProductSchema);
Cart Controller
router.post("/", async (req, res) => {
try {
const { userId } = req.body;
const cart = await Cart.findOne({ client: userId
}).populate("CartProduct");
if (cart === null) {
const newCart = new Cart({
client: userId,
});
await newCart.save();
return res.status(201).send({ cart: newCart });
}
res.status(200).send({ cart });
} catch (error) {
res.status(500).send(error);
}
});
Add Product to Cart
router.post("/addProductToCart", async (req, res) => {
try {
const { item, cart, quantity, price } = req.body;
const newProduct = new CartProduct({
item,
cart,
quantity,
totalPrice: price * quantity,
});
await newProduct.save();
await Cart.findOneAndUpdate(
{ _id: cart },
{ $push: { products: newProduct } },
{
new: true,
}
);
res.status(201).send({ message: "New Product Added To Cart" });
} catch (error) {
res.status(500).send(error);
}
});
adding product to cart does working,
but populate not working
adding the output
{
"cart": {
"active": true,
"products": [
"602bc081daf867167c2eb5da"
],
"_id": "602aab802f625d1654805ef0",
"client": "601c50211c94cf5d642c67fb",
"createAt": "2021-02-15T17:12:32.997Z",
"__v": 0
}
}
your missing { in cartSchema
const CartSchema = new Schema({
active: { type: Boolean, required: true, default: true },
createAt: { type: Date, default: Date.now },
client: { type: Schema.Types.ObjectId, ref: "User", required: true },
products: [{ type: Schema.Types.ObjectId, ref: "CartProduct" }],
});
export the the models like this
module.exports = Cart
There is way to make every time that CartProduct create, add automatically to? cart
there is not a automatic way for adding new _id of CartProduct to collection, so you should use findOneAndUpdate() or find() and push in to products array and save()
is this the right way of schemas structure
yes, It is.
so for populate you can try:
let resultCarts = await Cart.find(filter).populate("products")
let resultProducts = await Product.find(filter).populate("category")
so change the CartProduct to products because you should pass name of field as a argument not name of schema
await Cart.findOne({ client: userId
}).populate("products");

property pre does not exist on schema in mongoose

I am trying to detect changes in the document via pre hook but it typescript is giving me error that this property does not exist.
following Structured style, not OOP
// category.schema.ts
const categorySchema = new Schema({
category_id: { type: Number, required: true, unique: true },
name: { type: String, required: true, unique: true },
icon_url: { type: String, required: true },
items_quantity: { type: Number, required: true },
items: [
item_id: { type: Number, required: true, unique: true },
item_name: { type: String, required: true }
]
})
const Category: Model<Category> = model<Category>('Category', categorySchema);
export default Category;
Now I want to check for document changes on deletion of subdocument.
import CategorySchema from "../schemas/category.schema"; // schema path
router.delete('/:category/:item', async (req, res) => { // removes an item
let itemsQuantity: number;
let category = await CategorySchema.findOneAndUpdate(
{ category_id: req.params.category },
{ $pull: { items: { item_id: req.params.item } } },
{ new: true });
// pre does not exist
CategorySchema.pre('save', function(next) {
if(category.isModified()) {
log('changed');
} else {
log('not changed')
}
})
const data = await category.save();
res.status(200).send(req.params.item);
})
How to get or enable this hook, any suggestions?

Load specific mongoose models first

I'm attempting to use a model ("goal") in my "like" model class (Code below). However every time I startup the node instance it says that Goal.findById is not a function.
After running a console.log(number) in each model, I found that they load very oddly and out of the order I would like/need.
I was wondering how I can load models before others or set a specific load order for models?
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('../models/user.js');
var Goal = require('../models/goal.js');
var likeSchema = new Schema({
userPosted: {
type: Number,
ref: 'user',
required: true
},
goal: {
type: Number,
ref: 'goal',
required: true
}},
{
timestamps: true
});
likeSchema.post('save', function (doc, next) {
var goalID = doc.goal;
Goal.findOne({'_id': doc.goal}, function(err, goal) {
goal.likes.push(doc._id);
goal.save();
User.findById(doc.userPosted, function(err, user) {
user.likedPosts.push(goalID);
user.save();
next();
});
});
});
likeSchema.post('remove', function(doc) {
Goal.findById(doc.goal, function(err, goal) {
goal.likes.pull(doc._id);
goal.save();
User.findById(doc.userPosted, function(err, user) {
user.likedPosts.pull(goal._id);
user.save();
});
});
});
console.log("4");
module.exports = mongoose.model('like', likeSchema);
Error:
TypeError: Goal.findOne is not a function at model.<anonymous> (E:\Project\like.js:28:10)
**Edit: ** goal.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoIncrement = require('mongoose-auto-increment');
var User = require('../models/user.js');
var Like = require('../models/like.js');
var goal = new Schema({
user: {
type: Number,
ref: 'user',
required: true
},
user_name: {
type: String,
required: true
},
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
default: undefined,
trim: true
},
location: {
type: String,
default: undefined
},
likes: [{
type: Schema.ObjectId,
ref: 'like'
}],
comments: [{
type: Schema.ObjectId,
ref: 'comment'
}],
updates: [{
type: Schema.ObjectId,
ref: 'update'
}],
created: {
type: Date,
default: Date.now
},
cover_image: {
type: String,
default: undefined
},
complete_by: {
type: String,
default: "Death"
},
completed: {
type: Boolean,
default: false
},
completedDate: {
type: String,
default: undefined
},
url: {
type: String,
default: undefined
},
sponsor: {
type: String,
default: undefined
},
private: {
type: Boolean,
default: false
}
});
goal.plugin(autoIncrement.plugin, 'goal');
goal.pre('save', function (next) {
this.wasNew = this.isNew;
next();
});
goal.post('save', function (doc) {
if (this.wasNew) {
User.findById(doc.user, function (err, user) {
user.goals.push(doc._id);
user.save();
});
}
});
goal.post('remove', function(doc) {
//TODO: Removes like objects/user profile/anything containing the goal
User.findById(doc.user, function(err, user) {
user.goals.pull(doc._id);
user.save();
});
Like.find({
'goal': doc._id
}).remove(function(err, removed) {
if(err) {
console.log("ERROR?");
return;
}
console.log('removed likes - ' + removed);
});
});
module.exports = mongoose.model('goal', goal);
I think your likeschema would be something like this -
var likeSchema = new Schema({
userPosted: {
type: Number,
ref: 'User',
required: true
},
goal: {
type: Number,
ref: 'Goal',
required: true
}},
{
timestamps: true
});
If it doesn't work, can you please show your code for Goal model?

Resources