How to create an Insert trigger in mongodb atlas? - mongodb

I am new to NoSQL/MongoDB and I need to write a trigger on insert event on one of the collection in my database as follow: Database Name: GiftShop Collection Name: Gifts
Gifts Schema is as Follows:-
const mongoose = require("mongoose");
const userModel = require('./userModel');
const imageModel = require('./imageModel');
const giftSchema = mongoose.Schema({
name: String,
availableQuantity: Number,
price: Number,
Seller: {type: mongoose.Schema.Types.ObjectId,ref: 'UserModel'},
imageName: {type: mongoose.Schema.Types.ObjectId,ref: 'ImageModel'},
deliveryInDays: Number,category: [{type: String, ref: 'CategoryModel'}]
}, {collection: "gifts"});
module.exports = giftSchema;
I have another collection named as a notification. I want to create a trigger as such, as soon as the new gift gets inserted in gift collection the notification collection must get populated with the information having:
gift id,
seller id,
an array of buyer id.
the schema for notification is as follows:
const mongoose = require("mongoose");
const notificationSchema = mongoose.Schema({
seller: { type: mongoose.Schema.Types.ObjectId,ref: 'UserModel'},
buyer: [{type: mongoose.Schema.Types.ObjectId,ref: 'UserModel'}],
newGift: {type: mongoose.Schema.Types.ObjectId,ref: 'GiftModel'}
}, {collection: "notifications"});
module.exports = notificationSchema;
following is the function I wrote in MongoDB atlas but it has many issues with it:
exports = function (changeEvent) {
const {fullDocument} = changeEvent;
const {buyer, seller, newGift} = fullDocument;
const collection = context.services.get("Cluster0").db("test").collection("notification");
return collection.insertOne({buyer, seller, newGift})};
the setup I did on MongoDB atlas to create my trigger:
I am inserting a new gift via MongoDB compass which is connected to my MongoDB atlas but my notification collection is not getting populated with the data mentioned in the schema of notification. Where am I going wrong? and what is the correct way to write the trigger according to my requirement? Thank you.

There are some pointers for debugging stitch functions here, you should be able to follow those to determine if the issue is with your function or the trigger.

Related

Query db without certain elements inside an array

I set up a small database using a model and 2 schemas.
The model goes as follows:
const userSchema = new mongoose.Schema({
friendsRequests: [friendRequestSchema],
//other credentials that are not important//
});
And the friendRequestSchema:
const friendRequestSchema = new mongoose.Schema({
from: { type: Schema.Types.ObjectId, ref: "User" },
to: { type: Schema.Types.ObjectId, ref: "User" },
});
Basically friendsRequests is an array consisting of who requested to add the user to the friends list (which is the from property) and whom the user wants to add to their friends list (which is the to property).
For the query, I am trying to sort out how to send a response without containing the users that are inside the user's friendsRequests array.
If i do this :
const recFriends = await User.findOne({ _id: req.user }).select(
"friendsRequests"
);
i will get back the array with objects containing either sent or received requests. Now i want to query again the User model and have it not return elements from this array. How would i go about doing that?

MongoDb: In a many to many relation, when does it make sense to make the ids of both models in the definition of the other model

I have this relation:
User has many courses
I have implemented it by integrating the user_id in the definition of the Course model:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
//Create Schema
const CourseSchema = new Schema({
user: {
//This will associate the user by his id
type: Schema.Types.ObjectId,
ref: "users",
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = Course = mongoose.model("course", CourseSchema);
This way, if I want to get the courses created by a user X, I just use his id and look it up in the courses documents.
I thought with a big data base, this operation may be costly.
So I should add the course_id, to an array property called courses in the User model:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
courses: [
{
type: Schema.Types.ObjectId,
ref: "course",
},
],
});
module.exports = User = mongoose.model("users", UserSchema);
This way, I can simply populate that array with the .populate operation, instead of going with the appraoach mentioned above.
I would like to know if my way of thinking makes sense.
And whether there other reasons for me to add the course_id, to courses property in the User model.
both approach is correctly I have seen both methods that uses
but when you want to using populate, you should update two collections(users and courses) when you want to insert a new Course, so you should use transaction because if one was updated and the other did not(An error occurred) rollback be done
the first thing to understand about mongoose population is that it is not magic, but just a convenience method that allows you to retrieve related information without doing it all yourself
if user id be index in couresSchema you can find() all courses very fast,
but the generally recommendation is to consider the data usage patterns of your application and choose what is best

How to fetch specific fields of a sub-document population in mongoose

I have a User Model and Post Model as described below(required library imported)
User Schema
const UserSchema = new mongoose.Schema({
name: String,
email: String,
post: [ mongoose.Types.ObjectId, ref: 'posts' ]
});
Post Schema
const UserSchema = new mongoose.Schema({
title: String,
content: String,
postedOn: Date
});
I want to fetch post with respect to a user's id. But I don't want the whole Post document in return. I only want the attribute "Title" and "Date"
I tried the command:-
const posts = await User.findById(user_id).populate('post');
But it returns the entire collection. Can anyone tell me how can I fetch only "Title" and "Date" attribute of the post(sub-document) from User Model?
Mongoose population allows specific field selection by passing the usual field name syntax
const posts = await User.findById(user_id).populate('post', 'Title Date');

MongoDB (Mongoose) data structure question

I'm curious about the best way to represent this kind of situation in Mongo. I have my own idea, but I'm curious on what the general consensus/best practice actually would be.
Imagine I have two collections:-
Employees
--> _id
--> FirstName
--> Surname
--> Email
Comments
--> _id
--> PersonReference
--> CommentDate
--> Comment
Now imagine that Employees can come and go and the 'Employees' collection is always up-to-date. However, in the event that an employee has ever made a comment, the full information on the comment including who made it must be available.
The way I would propose to tackle this problem, is to organise the structure like this instead:-
Employees
--> _id: _id
--> FirstName: string
--> Surname: string
--> Email: string
Comments
--> _id: _id
--> CommentDate: date
--> Comment: string
[-] --> PersonReference
[+] --> Employee: object { _id: id, FirstName: string, Surname: string, Email:string }
So essentially, I would have a list of 'Active Employees' and at a time where a comment is made, I would duplicate the employee information into the Comments collection document (rather than use a reference).
From a high level perspective, is this considered best practise?
Many thanks
Duplicating the employee info in the comments collection is really a bad idea.
When an employee info needs to be changed, it will also needs to be updated in the comments.
You have a few options:
1-) Embedding the comments inside the Employee schema:
In this method we have no separate Comments collection.
If you have no need to independently query comments, this method makes sense.
This way we can access a user and his/her comments in one db access and without needing any join (populate or lookup).
The schema for this can be like this:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
new mongoose.Schema({
commentDate: Date,
comment: String
})
]
});
module.exports = mongoose.model("Employee", employeeSchema);
2-) Parent referencing:
In this method we keep the reference of the comments in the Employee schema.
If you don't need to access to employee from a comment, this can an option.
Employee Schema:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String
});
module.exports = mongoose.model("Comment", commentSchema);
3-) Child referencing
In this method we keep reference of the employee in the comments.
So if you need to access the comments from an employee we need to use Populate Virtual feature of mongoose. Becase in employee schema we don't have a reference to the comments.
Employee Schema:
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema(
{
firstName: String,
username: String,
email: String
},
{
toJSON: { virtuals: true } // required to use populate virtual
}
);
// Populate virtual
employeeSchema.virtual("comments", {
ref: "Comment",
foreignField: "employee",
localField: "_id"
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String,
employee: {
type: mongoose.Schema.Types.ObjectId,
ref: "Employee"
}
});
module.exports = mongoose.model("Comment", commentSchema);
4-) Both parent and child referencing:
With this method, it is possible to select comments from employee, and employee from comments. But here we have some kind of data duplication, and also when a comment is deleted, it needs to be done in both of the collections.
const mongoose = require("mongoose");
const employeeSchema = new mongoose.Schema({
firstName: String,
username: String,
email: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Employee", employeeSchema);
Comment Schema:
const mongoose = require("mongoose");
const commentSchema = new mongoose.Schema({
commentDate: Date,
comment: String,
employee: {
type: mongoose.Schema.Types.ObjectId,
ref: "Employee"
}
});
module.exports = mongoose.model("Comment", commentSchema);
Many database implement kind of no-delete collections, implementing a delete/active flag for each document.
For example, Employees collection would become :
Employees
--> _id: _id
--> FirstName: string
--> Surname: string
--> Email: string
--> Active: boolean
This way, you keep track on employees data that has been deleted, and prevent documents duplication if you have database size restrictions.
PS: nowadays you can be tackled keeping user data if they ask deletion (RGPD)
EDIT: This solution with boolean may not work if Employees document is updated and you want to keep employees firstname,name,mail,etc at the time he made the Comment.

MongoDB Mongoose save object with nested objects

I'm new to MongoDB and I'm creating a simple db with Mongoose with the following models: User, Game and Players.
So, one user contains none or many games. Every game has to players, and each player refers to a user. Like this (I simplified the schemas for clarity):
const UserSchema = new Schema({
name: String,
games: [{
type: Schema.Types.ObjectId,
ref: 'game'
}]
});
const GameSchema = new Schema({
mode: Number,
players: {
type: [{
type: Schema.Types.ObjectId,
ref: 'player'
}],
required: true
}
});
const PlayerSchema = new Schema({
order: Number,
isWinner: Boolean,
user: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
}
});
So, now in the frontend I want to send a petition to the backend to create a new game for users Joe (_id:11111) and Bob (_id:22222) and so I send a POST to /api/games with the body { users: [ 11111, 22222 ] }
Now my question is, for the backend to create a new game, it also has to create 2 players. What's the best way to achieve this?
In the Game.create() method, shall I retrieve the data, create and save the players, create the game, assign the players, save the game, and also update the users and add the game ids?
I also read about Mongoose middleware, where you can set certain functions to be executed before or after some operations. So maybe it's better:
pre function before Game.create, to create the players
post function before Game.create, to update the users
This last one seems cleaner.
What's the best way? Maybe another one I have not considered?
Thanks
I would suggest you using the post and pre functions defined in the mongoose middleware. They're pretty straightforward and neat to use. It will probably solve your problem.
Here is a personal example of a problem we had; In our case, we had to assign a userId from a sequence in the database. We used the following code:
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
My suggestion is that before you create the game, you can search for the users and add a reference to the game. If I were you, I would use the findAndModify query of mongodb to find the users or create if they do not exist yet.