I want to get some basics in mongo, but I am stuck as I am not sure what I am doing wrong.
So, I have two documents: Race and Building.
My schema for race looks like this:
import mongoose, { Model, Schema, Types } from "mongoose";
export interface IRaceSchema {
name: string;
buildings: Types.ObjectId;
description: string;
}
const raceSchema = new mongoose.Schema<IRaceSchema, Model<IRaceSchema>>(
{
name: {
type: String,
unique: true,
},
description: String,
buildings: [
{
type: Schema.Types.ObjectId,
ref: "Building",
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
raceSchema.pre(/^find/, function (next) {
this.populate({
path: "buildings",
match: { race: { $eq: this.name } },
});
next();
});
export const Race = mongoose.model<IRaceSchema>("Race", raceSchema);
In here i have populate from my building document.
My buildingModel looks like this:
import mongoose, { Model, Schema, Types } from "mongoose";
interface IBuildingSchema {
name: string;
description?: string;
race: string;
buildingType?: string;
level: number;
bonus?: string[];
}
const buildingSchema = new mongoose.Schema<
IBuildingSchema,
Model<IBuildingSchema>
>(
{
name: String,
description: String,
buildingType: String,
level: {
type: Number,
default: 1,
},
bonus: [String],
race: {
type: String,
enum: ["orc", "human", "elvs", "nekro"],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
export const Building = mongoose.model<IBuildingSchema>(
"Building",
buildingSchema
);
My issue is, that I want to populate my Race document with buildings based on race assigned to my building
I am using $eq but not sure if this is proper way, as nothing is populating
Related
I want to add an element to the array of all collections in the city collection, but Mongo creates the ID as duplicate.
this is my code
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
City Schema
export class BaseCity extends Document {
#Prop({
type: String,
required: true,
})
_id: string;
#Prop({ type: String, unique: true })
code: string;
#Prop({ type: String, ref: Province.name })
province: string | Province;
#Prop({ type: String })
faName: string;
}
#Schema({ timestamps: true })
#Schema({ collection: 'city', virtuals: true, _id: false, timestamps: true })
export class City extends BaseCity {
#Prop({ type: String })
imageName: string;
#Prop({ index: true, type: String })
enName: string;
#Prop({ type: Number })
displayOrder: number;
#Prop({ type: Boolean })
isFeatured: boolean;
#Prop({ type: Boolean })
isEnabled: boolean;
#Prop({ type: Coordinate })
coordinate: Coordinate;
#Prop([{ type: Region, ref: Region.name, default: [] }])
region: Region[];
#Prop([{ type: SubMenu }])
subMenu: SubMenu[];
#Prop([{ type: CityTags }])
tags: CityTags[];
}
const CitySchema = SchemaFactory.createForClass(City);
CitySchema.index({ faName: 'text' });
export { CitySchema };
DB
As you can see, ID 63ec8f47efbd82c8face341a is duplicated in all documents.
Is there a solution to solve this problem?
To avoid duplicate IDs, you could use the $addToSet instead of $push. The $addToSet adds an element to an array only if it does not already exist in the set.
Check this:
await this.cityRepository.updateMany(
{},
{
$addToSet: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
Update:
To keep unique ids
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
_id: new ObjectId(),
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
There are currently 2 models Product and Books; where Book inherits from Product as shown below:
const ProductSchema = new mongoose.Schema(
{
name: {...},
description: {...},
images: [{... }],
inventory: { ... },
department: { ... },
....
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
discriminatorKey: "kind",
}
)
Model = mongoose.model("Product", productSchema)
const BookSchema = new mongoose.Schema({
subtitle: { ... },
abstract: { ... },
publisher: { ... },
authors: { ... },
...
},
{
timestamps: true, discriminatorKey: "kind",
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
)
Book = Product.discriminator("Book", BookSchema)
Additionally, there is a Cart schema, which has a subdocument `products` that includes a referenced field `bookID` as shown below:
const cartItem = new mongoose.Schema({
productID: {
type: mongoose.Types.ObjectId,
ref: "Product",
required: [true, "Please provide productID. "]
},
quantity: { ... },
sessionID: { ... },
})
const cartSchema = new mongoose.Schema({
products: [cartItem],
active: {
type: Boolean,
default: true,
hide: true,
},
sessionID: {
type: mongoose.Types.ObjectId,
ref: "Session"
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
})
Cart = mongoose.model("Cart", cartSchema)
I am using mongoosejs v-6.8.3
The issue is that .populate() on Cart instances returns only the fields from Book model (without including the fields from Product model).
newCart = new Cart({...})
newCart.save()
let populatedCart = await newCart.populate({ path: "products.productID", model: Product})
Your problem is probably a result of how Mongoose's discriminator feature functions. Mongoose will build a new collection for the child model and store its documents in that collection when you use the discriminator method to generate a new model that inherits from an existing model. The only collection that is queried when populate is run on the Cart instances, which only populates the fields from the Book model.
One potential fix for this problem is to explicitly include the fields from the parent model in the child model's schema. In this manner, the fields from the Product model will be included when fill is executed on the Cart instances. Another alternative is to execute the fill method with the choose option selected to include the desired fields from the parent model.
Here's an example:
const ProductSchema = new mongoose.Schema({
name: {...},
description: {...},
images: [{... }],
inventory: { ... },
department: { ... },
....
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
discriminatorKey: "kind"
});
const Model = mongoose.model("Product", productSchema);
const BookSchema = new mongoose.Schema({
subtitle: { ... },
abstract: { ... },
publisher: { ... },
authors: { ... },
...
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
discriminatorKey: "kind",
});
const Book = Model.discriminator("Book", BookSchema);
const cartItem = new mongoose.Schema({
productID: {
type: mongoose.Types.ObjectId,
ref: "Product",
required: [true, "Please provide productID. "]
},
quantity: { ... },
sessionID: { ... },
});
const cartSchema = new mongoose.Schema({
products: [cartItem],
active: {
type: Boolean,
default: true,
},
sessionID: {
type: mongoose.Types.ObjectId,
ref: "Session"
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
});
const Cart = mongoose.model("Cart", cartSchema);
let newCart = new Cart({...});
await newCart.save();
let populatedCart = await Cart.findById(newCart._id).populate({ path: "products.productID", model: "Product", select: '-__v' });
console.log(populatedCart);
Suppose I have Auction Schema as follows
import { Document, model, Schema } from "mongoose";
import { IProduct } from "./product";
const AuctionSchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
},
products: [{ type: Schema.Types.ObjectId, ref: "Product" }],
status: { type: Boolean, default: false },
duration: { type: Number },
start: { type: Date },
end: { type: Date },
},
{ timestamps: true }
);
export interface IAuction extends Document {
name: string;
status?: boolean;
products: IProduct["_id"][];
start?: Date;
end?: Date;
duration?: number;
}
const Auction = model<IAuction>("Auction", AuctionSchema);
export default Auction;
I want to update this auction related field i.e status to false automatically if the field start and end are same
How to do this?
I'm learning express.js and mongoDB and want to create a functionality, where user should be able add products.
My code contains 2 models, User and Product. User has a reference to Product. In User query when i try to populate Product', null is returned.
I'm testing it with postman, and the result is null in cart.
I am a beginner and don't understand how to solve the issue.
user schema
import mongoose, { Document } from 'mongoose'
import { ProductDocument } from './Products'
export type UserDocument = Document & {
firstName: string;
lastName: string;
password: string;
email: string;
isAdmin: boolean;
cart: ProductDocument[];
resetLink: string;
}
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
max: 64,
},
lastName: {
type: String,
required: true,
max: 64,
},
password: {
type: String,
required: true,
min: 6,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
isAdmin: {
type: Boolean,
default: false,
},
cart: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
},
],
resetLink: {
data: String,
default: '',
},
})
export default mongoose.model<UserDocument>('Users', userSchema)
product schema
import mongoose, { Document } from 'mongoose'
export type ProductDocument = Document & {
name: string;
description: string;
categories: string[];
variants: string[];
sizes: number[];
}
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true,
},
description: {
type: String,
},
categories: [String],
variants: [String],
sizes: [Number],
})
export default mongoose.model<ProductDocument>('Products', productSchema)
and the controller for responsible for populating
export const getProduct = async (req: Request, res: Response) => {
try {
const { productId } = await req.body
const productBought = await Products.findOne({ _id: productId }).then(
(product) => {
return product
}
)
console.log(productBought)
const updated = await Users.findOneAndUpdate(
{ _id: req.params.userId },
{ $push: { cart: productBought } },
{ new: true }
)
.populate('cart')
.exec()
return res.json(updated)
} catch (error) {
return res.status(404).json({ message: 'does not work' })
}
}
output from postman
{
"isAdmin": false,
"cart": [
null,
null
],
"_id": "5f894b3c06b4a108f8d9a7ab",
"firstName": "John",
"lastName": "Doe",
"password": "$2b$08$rA0/r8iVBWeSyyerPDpPWO.ztgouQoseX0QFBZ.mlPgb6tELlrhpy",
"email": "john#gmail.com",
"__v": 0
}
your "cart" like[ObjectId,ObjectId], but your "productBought" like[{},{}].
try let cart = [];productBought.forEach((v,k) => {cart[k] = v._id});
and use { $push: { cart: { $each: cart } },
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?