I am working with 2 models:
Product and Event.
Product model has reference from Event (_id) Event model has reference from Product (_id).
So far I've found that if I want to update an Event and add or quit products, I have to update the Product model as-well using another operation. The same for delete (if I delete an event, I have to update the products that had that event associated and take them out).
My problem here is that for example: I have a Product (id 1) that has event_id:[1,2,3,4}] I have Event (id 1) that has a product_id: [1,2].
If I update the event (id 1) and take out the product 1, the Event now will have only product id 2, but the product will still have the event id 1.
For doing that I am using hooks: for updating that in particular I am doing this:
EventSchema.post("findOneAndUpdate", function(doc) {
Product.updateMany({
_id: {
$in: doc.product
}
}, {
event: doc._id.toString()
}, {
multi: true
}, function(error, product) {
if (error) console.log(error)
})
})
How can I update the Product model, taking out the id in event_id column array? I hope my explanation in clear.
EVENT SHCEMA
const EventSchema = new Schema({
client: {
type: [{
type: Schema.Types.ObjectId,
ref: 'Client'
}]
},
product: {
type: [{
type: Schema.Types.ObjectId,
ref: 'Product'
}]
},
date: {
type: Date,
maxlength: 64,
lowercase: true,
trim: true
},
place: {
type: String,
maxlength: 1200,
minlength: 1,
},
price: {
type: Number
},
comment: {
type: String,
maxlength: 12000,
minlength: 1,
},
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
},
);
CLIENT SCHEMA
const ClientSchema = new Schema(
{
first_name: {
type: String,
maxlength: 64,
minlength: 1,
required: [true, "Client first name is required"]
},
last_name: {
type: String,
maxlength: 64,
minlength: 1
},
address: {
type: String,
maxlength: 1200,
minlength: 1
},
phone: {
type: String,
maxlength: 64,
minlength: 1
},
email: {
type: String,
maxlength: 64,
lowercase: true,
trim: true
},
web: {
type: String,
maxlength: 1200,
minlength: 1
},
comment: {
type: String,
maxlength: 12000,
minlength: 1
},
event: {
type: [
{
type: Schema.Types.ObjectId,
ref: "Event"
}
]
}
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
}
);
PRODUCT SCHEMA:
const ProductSchema = new Schema(
{
name: {
type: String,
maxlength: 64,
minlength: 1,
required: [true, "Product name is required"]
},
description: {
type: String,
maxlength: 12000,
minlength: 1
},
comment: {
type: String,
maxlength: 12000,
minlength: 1
},
state: {
type: String,
maxlength: 64,
minlength: 0
},
available: {
type: Boolean,
default: true
},
price: {
type: Number
},
category: {
type: [
{
type: Schema.Types.ObjectId,
ref: "Category"
}
]
},
event: {
type: [
{
type: Schema.Types.ObjectId,
ref: "Event"
}
]
},
image: {
type: [
{
type: Schema.Types.ObjectId,
ref: "Image"
}
]
}
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
}
);
INPUT DATA: product/:id route
{
"available": false,
"category": [
"5e04cd609b1c6812c8f9a9d1"
],
"event": [
"5e07f73d72503f6659f40b26"
],
"image": [
"5e05f9dd66432544b7bf0221"
],
"_id": "5e05f9d166432544b7bf0220",
"name": "Mesa ratona",
"price": 1200,
"comment": "-",
"description": "-",
"__v": 0,
"modelName": "Product",
"id": "5e05f9d166432544b7bf0220"
}
INPUT DATA event/:id
{
"client": [
"5e05f743cd57804386a92a89"
],
"product": [
"5e05f9d166432544b7bf0220",
"5e06464b811a954e831eafcf",
"5e064c5c811a954e831eafd3"
],
"_id": "5e07f73d72503f6659f40b26",
"date": "2020-01-12T00:45:33.069Z",
"place": "EVENT 1",
"price": 123,
"comment": "EVENT 1",
"__v": 0,
"id": "5e07f73d72503f6659f40b26"
}
INPUT DATA client/:id
{
"event": [
"5e07f73d72503f6659f40b26"
],
"_id": "5e05f743cd57804386a92a89",
"first_name": "Ernesto",
"last_name": "De Lucía",
"address": "Calle Santa Fé 3129 Piso 5 Depto \"B\" Capital Federal",
"email": "ernestodl@gmail.com",
"phone": "+54 011 1567432984",
"web": "-",
"comment": "-",
"__v": 0,
"full_name": "Ernesto De Lucía",
"id": "5e05f743cd57804386a92a89"
}
Thank you!
In your schemas there are many to many references, so when an items needs to be deleted it needs to be deleted from all the referenced documents.
I suggest you to remove that many to many references, by using virtual populate.
I simplified the schemas to include only the required fields.
Event: ( I removed the references to the products and clients, and used virtual populate)
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const EventSchema = new Schema(
{
name: {
type: String,
required: true
}
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
}
);
// Virtual populate
EventSchema.virtual("clients", {
ref: "Client",
foreignField: "events",
localField: "_id"
});
EventSchema.virtual("products", {
ref: "Product",
foreignField: "events",
localField: "_id"
});
module.exports = mongoose.model("Event", EventSchema);
Client:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ClientSchema = new Schema(
{
first_name: {
type: String,
maxlength: 64,
minlength: 1,
required: [true, "Client first name is required"]
},
events: [
{
type: Schema.Types.ObjectId,
ref: "Event"
}
]
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
}
);
module.exports = mongoose.model("Client", ClientSchema);
Product:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ProductSchema = new Schema(
{
name: {
type: String,
maxlength: 64,
minlength: 1,
required: [true, "Product name is required"]
},
events: [
{
type: Schema.Types.ObjectId,
ref: "Event"
}
]
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true }
},
{
timestamps: true
}
);
module.exports = mongoose.model("Product", ProductSchema);
I have these sample documents:
Events:
{
"_id": "5e08ceb5a62f094ba4ca2542",
"name": "Event 1",
"__v": 0,
"id": "5e08ceb5a62f094ba4ca2542"
}
{
"_id": "5e08cec5a62f094ba4ca2543",
"name": "Event 2",
"__v": 0,
"id": "5e08cec5a62f094ba4ca2543"
}
Products:
{
"events": [
"5e08ceb5a62f094ba4ca2542",
"5e08cec5a62f094ba4ca2543"
],
"_id": "5e08cf9de9dfcc0e1431c90b",
"name": "Product 1",
"__v": 0,
"id": "5e08cf9de9dfcc0e1431c90b"
}
{
"events": [
"5e08ceb5a62f094ba4ca2542"
],
"_id": "5e08cfa9e9dfcc0e1431c90c",
"name": "Product 2",
"__v": 0,
"id": "5e08cfa9e9dfcc0e1431c90c"
}
Clients:
{
"events": [
"5e08ceb5a62f094ba4ca2542",
"5e08cec5a62f094ba4ca2543"
],
"_id": "5e08cfdce9dfcc0e1431c90e",
"first_name": "Client 1",
"__v": 0,
"id": "5e08cfdce9dfcc0e1431c90e"
}
{
"events": [
"5e08ceb5a62f094ba4ca2542"
],
"_id": "5e08cfece9dfcc0e1431c90f",
"first_name": "Client 2",
"__v": 0,
"id": "5e08cfece9dfcc0e1431c90f"
}
Let's first solve the problem of accessing clients and products from event since we removed that references.
We can use the following route to get events with its clients and products virtual populated:
router.get("/events/:id", async (req, res) => {
const result = await Event.findById(req.params.id)
.populate("clients")
.populate("products");
res.send(result);
});
The response will be like this: (as you see even though we removed the references we could access clients and products using virtual populate)
{
"_id": "5e08ceb5a62f094ba4ca2542",
"name": "Event 1",
"__v": 0,
"clients": [
{
"events": [
"5e08ceb5a62f094ba4ca2542",
"5e08cec5a62f094ba4ca2543"
],
"_id": "5e08cfdce9dfcc0e1431c90e",
"first_name": "Client 1",
"__v": 0,
"id": "5e08cfdce9dfcc0e1431c90e"
},
{
"events": [
"5e08ceb5a62f094ba4ca2542"
],
"_id": "5e08cfece9dfcc0e1431c90f",
"first_name": "Client 2",
"__v": 0,
"id": "5e08cfece9dfcc0e1431c90f"
}
],
"products": [
{
"events": [
"5e08ceb5a62f094ba4ca2542",
"5e08cec5a62f094ba4ca2543"
],
"_id": "5e08cf9de9dfcc0e1431c90b",
"name": "Product 1",
"__v": 0,
"id": "5e08cf9de9dfcc0e1431c90b"
},
{
"events": [
"5e08ceb5a62f094ba4ca2542"
],
"_id": "5e08cfa9e9dfcc0e1431c90c",
"name": "Product 2",
"__v": 0,
"id": "5e08cfa9e9dfcc0e1431c90c"
}
],
"id": "5e08ceb5a62f094ba4ca2542"
}
Now to remove an event from a product or client, we need to just remove from there.
For example to delete an event from a product, we just need to delete it from product:
router.delete("/products/:id/:eventId", async (req, res) => {
const result = await Product.findByIdAndUpdate(
req.params.id,
{
$pull: {
events: req.params.eventId
}
},
{ new: true }
);
res.send(result);
});
Before this delete, this product had 2 events with ids 5e08ceb5a62f094ba4ca2542
and 5e08cec5a62f094ba4ca2543
.
After we delete it will only have one event with id 5e08ceb5a62f094ba4ca2542
:
{
"events": [
"5e08ceb5a62f094ba4ca2542"
],
"_id": "5e08cf9de9dfcc0e1431c90b",
"name": "Product 1",
"__v": 0,
"id": "5e08cf9de9dfcc0e1431c90b"
}