I have the following Schema -
const leadSchema = new Schema(
{
emails: [{ type: Email, default: null }],
name: { type: String },
country: { type: String },
city: { type: String, index: true },
source: {
type: Number,
min: 1,
max: leadConfig.sources.length,
required: true
},
course: { type: Schema.Types.ObjectId, ref: 'courses',required: true},
gender: { type: String, enum: leadConfig.gender },
status: {type: Schema.Types.ObjectId, ref: 'status' },
dob: Date,
parent_name: String,
counselor: { type: Schema.Types.ObjectId, ref: 'users', default: null },
consultant_amount: { type: Number, min: 0, default: 0 },
consultant_amount_paid: { type: Number, min: 0, default: 0 },
loan: { type: Boolean, default: false },
reported: { type: Boolean, default: false },
scholarship: { type: Number, default: 0 },
student_id: { type: Number, default: null },
next_interection_deadline: { type: Date, default: null },
session: { type: Schema.Types.ObjectId, ref: 'session' }
},
{ timestamps: true }
);
module.exports = mongoose.model('leads', leadSchema);
I want to store the update history of all the documents of this collection.
For Example -
If I change the name field of a lead from 'John' to 'Jane' then a record should be saved in a history table with the following schema -
{
_id:(ObjectId),
collectionName:"lead"
column_name:"name"
oldValue - 'John',
newValue - 'Jane'
updateAt - Date()
}
I googled some plugins like mongoose-diff-history and it serves the purpose well but the only drawback was that it only worked with .save() method and not with mongodb updates methods.
I have been working on this problem for so many days but couldn't find a correct and efficient solution. Any solutions to this problem will be very much appreciated.
Have you looked into the midldeware hooks? Usually what you want could be handled there. For example look into Mongoose hooks: http://mongoosejs.com/docs/middleware.html
You have basically "events" which allow you do intercept records just before "save" etc and do something (like in your case store/log somewhere).
Here is an example from their docs:
var schema = new Schema(..);
schema.pre('save', function(next) {
// do stuff
next();
})
Here is one for the 'update':
schema.pre('update', function() {
this.update({},{ $set: { updatedAt: new Date() } });
});
Related
I am using the mongoose updateMany() method and I also want to keep it a part of transaction. The documentation shows the example of save() where I can do something like Model.save({session: mySession}) but don't really know how to use it with for example Model.updateMany()
UPDATE:
For example I have two models called SubDomain and Service and they look like this respectively:
SUB-DOMAIN
{
name: {
type: String,
required: true,
},
url: {
type: String,
required: true,
unique: true,
},
services: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Service",
},
],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
}
SERVICE:
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: { type: Number },
tags: { type: Array },
packages: [
{
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
},
],
map: { type: String },
isHidden: {
type: Boolean,
required: true,
default: false,
},
sortingOrder: { type: Number },
isForDomain: { type: Boolean, required: false, default: false },
isForSubDomain: { type: Boolean, required: false, default: false },
subDomains: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "SubDomain",
},
],
}
Now the main field here is the services field in SubDomain and subDomains field in Service.
The complicated part😅:
Whenever the user wants to create new service, I want to $push that service's _id into the array of services of all the subDomains inside that new service
And for that, I am using the updateMany() like this:
const sess = await mongoose.startSession();
sess.startTransaction();
const newService = new Service({
_id: mongoose.Types.ObjectId(),
subDomains: req.body.subDomains
...foo
})
await SubDomain.updateMany(
{ _id: { $in: req.body.subDomains } },
{ $push: { services: newService._id } }
);
The problem starts here, of course I can do:
newService.save({session: sess})
but how do I keep my SubDomain's updateMany in the same transaction (i.e sess)
I know my example is difficult to wrap your head around but I have tried to pick a simplest example rather than copying the exact same code which would have been a lot more difficult
I have a database named "reviews" with a 9.7GB size. It has a collection name products. I was able to optimize the READ request using indexing technical by running the command db.products.ensureIndex({product_name: 1}); When I run the following command db.products.find({product_name:"nobis"}).explain("executionStats"); in MongoDB terminal, it shows that my execution time reduces from 28334ms to 3301ms.
I have the following 2 questions:
1) How do I use explain("executionStats"); on CREATE, PUT and DELETE requests? For example, I got this following error [thread1] TypeError: db.products.insert(...).explain is not a function when I tried to use the following insert function
db.products.insert({"product_id": 10000002,"product_name": "tissue","review": [{"review_id": 30000001,"user": {"user_id": 30000001,"firstname": "Peter","lastname": "Chen","gender": "Male","nickname": "Superman","email": "hongkongbboy#gmail.com","password": "123"},"opinion": "It's good","text": "It's bad","rating_overall": 3,"doesRecommended": true,"rating_size": "a size too big","rating_width": "Slightly wide","rating_comfort": "Uncomfortable","rating_quality": "What I expected","isHelpful": 23,"isNotHelpful": 17,"created_at": "2007-10-19T09:03:29.967Z","review_photo_path": [{"review_photo_id": 60000001,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}, {"review_photo_id": 60000002,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}]}, {"review_id": 30000002,"user": {"user_id": 30000002,"firstname": "Peter","lastname": "Chen","gender": "Male","nickname": "Superman","email": "hongkongbboy#gmail.com","password": "123"},"opinion": "It's good","text": "It's bad","rating_overall": 3,"doesRecommended": true,"rating_size": "a size too big","rating_width": "Slightly wide","rating_comfort": "Uncomfortable","rating_quality": "What I expected","isHelpful": 23,"isNotHelpful": 17,"created_at": "2007-10-19T09:03:29.967Z","review_photo_path": [{"review_photo_id": 60000003,"review_photo_url": "https://sdcuserphotos.s3.us-west-1.amazonaws.com/741.jpg"}]}]}).explain("executionStats");
2) Is there any performance Optimization method I can use for the CREATE, PUT and DELETE requests? For example, I am able to use POSTMAN to get the response time of a DELETE request, but the response time takes 38.73seconds.
const deleteReview = (request, response) => {
const id = parseInt(request.params.id);
Model.ProductModel.findOneAndDelete({ "review.review_id": id}, (error, results) => {
if (error) {
response.status(500).send(error);
} else {
response.status(200).send(results);
}
});
};
This is my MongoDB schema:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/reviews', { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true });
const Schema = mongoose.Schema;
const productSchema = new Schema({
product_id: { type: Number, required: true, unique: true },
product_name: { type: String, required: true, unique: true },
review: [{
review_id: { type: Number, required: true, unique: true },
user: {
user_id: { type: Number },
firstname: { type: String },
lastname: { type: String },
gender: { type: String, enum: ['Male', 'Female', 'Other'] },
nickname: { type: String },
email: { type: String, required: true },
password: { type: String, required: true },
},
opinion: { type: String, required: true },
text: { type: String },
rating_overall: { type: Number, min: 1, max: 5, required: true },
doesRecommended: { type: Boolean, required: true },
rating_size: { type: String, enum: ['a size too small', '1/2 a size too small', 'Perfect', '1/2 a size too big', 'a size too big'], required: true },
rating_width: { type: String, enum: ['Too narrow', 'Slightly narrow', 'Perfect', 'Slightly wide', 'Too wide'], required: true },
rating_comfort: { type: String, enum: ['Uncomfortable', 'Slightly uncomfortable', 'Ok', 'Comfortable', 'Perfect'], required: true },
rating_quality: { type: String, enum: ['Poor', 'Below average', 'What I expected', 'Pretty great', 'Perfect'], required: true },
isHelpful: { type: Number, required: true, default: 0 },
isNotHelpful: { type: Number, required: true, default: 0 },
created_at: { type: Date, required: true },
review_photo_path: [{
review_photo_id: { type: Number },
review_photo_url: { type: String }
}]
}]
});
const ProductModel = mongoose.model('product', productSchema);
module.exports = { ProductModel };
If you do not have one, ensure you have an index of review.review_id on your products collection. You're using that to look up what to delete so it should be indexed.
I read your deleteReview function as deleting the product document that contains the review, not just removing the individual review -- is that what you expect?
You should be able to just $pull the review from the reviews array to get rid of it.
You can use explain on an update like so:
db.products.explain().update({...}, {...});
See: https://docs.mongodb.com/manual/reference/method/db.collection.explain/
You can explain:
aggregate()
count()
find()
remove()
update()
distinct()
findAndModify()
I'm currently trying to figure out at mongodb what's the best way in terms of performance cost and redundancy the best way of building a big document data schema. The final JSON from my rest -> app will be likely how it is structured.
Now internally the data will not be used as many to many that's why i binded it into a single document. Only the id will be used as a reference in another collections.
What you guys think, is it better to spit as relational way, with multiple collection to store the content inside of deliverable and use reference or just embedded. (since NoSQL has no joins i though this way will speed up)
Current using mongoose at node app
The Schema:
projectSchema = new Schema({
name: {
type: String,
required: true,
minlength: 3,
maxlength: 50
},
companyId: {
type: mongoose.Types.ObjectId,
ref: 'companies',
required: true
},
deleted: {
type: Number,
enum: [0, 1],
default: 0
},
predictedStartDate: {
type: Date,
default: ""
},
predictedEndDate: {
type: Date,
default: ""
},
realStartDate: {
type: Date,
default: ""
},
realEndDate: {
type: Date,
default: ""
},
//not final version
riskRegister: [{
name: String,
wpId: {
type: mongoose.Types.ObjectId,
ref: 'projects.deliverables.workPackages.id',
required: true
},
probability: String,
impact: String,
riskOwner: String,
response: String,
duration: String,
trigger: String,
status: String,
plannedTimming: String
}],
deliverables: [{
body: String,
workPackages: [{
body: String,
activities: [{
body: String,
tasks: [{
content: String,
properties: [{
dependecies: Array,
risk: {
type: Number,
enum: [0,1],
required: true
},
estimatedTime: {
type: Number,
required: true
},
realTime: {
required: true,
default: 0,
type: Number
},
responsible: {
id: {
type: Number,
default: -1
},
type: {
type: String,
enum: [0, 1], //0 - user, 1 - team
default: -1
}
},
materialCosts: {
type: Number,
default: 0
},
status: {
type: Number,
default: 0
},
approval: {
type: Number,
default: 0
},
startDate: {
type: Date,
default: ""
},
finishDate: {
type: Date,
default: ""
},
endDate: {
type: Date,
default: ""
},
userStartDate: {
type: Date,
default: ""
},
endStartDate: {
type: Date,
default: ""
},
taskNum: {
type: Number,
required: true
},
lessonsLearn: {
insertedAt: {
type: Date,
default: Date.now
},
creatorId: {
type: mongoose.Types.ObjectId,
ref: 'users',
required: true
},
situation: {
type: String,
required: true
},
solution: {
type: String,
required: true
},
attachments: Array
}
}]
}]
}]
}]
}]
})
The only concern I would raise would be regarding deliverables. If in the future there is a use case to do some CRUD operation regarding activities or tasks on the workPackage, the mongodb position operator $ does not support inner arrays, so you would be forced to extract all the deliverables and in memory iterate over all and only after update the deliverables.
My sugestion would be to support only arrays in the first level on the object. The inner objects should be moduled in separate collection ( activities and tasks ). In latest versions of mongodb you now have support to transactions so you can implement ACID on your operations against database, so the manipulation of all this information can be done in an atomic way.
So my goal is to retrieve posts with comments that are placed today with Mongoose.
First, I create a start-of-the-day UTC current date object with:
const todayForEvent = moment().startOf('day')
.utc().toDate();
this results in 2019-01-02T06:00:00.000Z
then I want to create a DB search with mongoose to fetch the posts where a comment has been placed today
const posts = await Post.find({
// From this user...
$and: [
// Find normal posts that has comments (recent interactions)
{ _posted_by: userId },
{ comments: { $exists: true, $ne: [] } },
{ 'comments.created_date': { $gte: todayForEvent } }
]
})
Third, I have mongoose comment documents that have a property created_date
const CommentSchema = new Schema({
created_date: {
type: Date,
default: moment().utc().toDate()
}
});
const Comment = mongoose.model('Comment', CommentSchema);
module.exports = Comment;
This is the result document after placing a comment
Everything looks OK but for some reason the posts array is still empty after the database search, can someone please tell me what I did wrong
EDIT: added post schema at request
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PostSchema = new Schema({
content: {
type: String,
trim: true
},
_content_mentions: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
type: {
type: String,
required: true,
enum: ['normal', 'event', 'task']
},
_liked_by: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
comments_count: {
type: Number,
default: 0
},
comments: [{
type: Schema.Types.ObjectId,
ref: 'Comment'
}],
_group: {
type: Schema.Types.ObjectId,
ref: 'Group',
required: true
},
_posted_by: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
task: {
due_to: {
type: String,
default: null
},
_assigned_to: {
type: Schema.Types.ObjectId,
ref: 'User'
},
status: {
type: String,
enum: ['to do', 'in progress', 'done']
}
},
event: {
due_to: {
type: Date,
default: null
},
_assigned_to: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
},
created_date: {
type: Date,
default: Date.now
},
files: [{
orignal_name: {
type: String,
default: null
},
modified_name: {
type: String,
default: null
}
}]
});
const Post = mongoose.model('Post', PostSchema);
module.exports = Post;
EDIT 2: sample post document
{ _id: 5c2d14c30176ac30204809a8,
task: { due_to: null },
event: { due_to: null, _assigned_to: [] },
_content_mentions: [],
_liked_by: [],
comments_count: 1,
comments: [ 5c2d14dc0176ac30204809ab ],
content: '<p>poging 5 duust</p>',
type: 'normal',
_posted_by:
{ _id: 5c292e0e63deb43d9434f664,
profile_pic: 'default_user.png',
first_name: 'Jaspet',
last_name: 'Houthoofd' },
_group: 5c292db763deb43d9434f660,
created_date: 2019-01-02T19:45:07.710Z,
files: [],
__v: 0,
liked_by: [] }
**EDIT 3: sample comment **
{ _content_mentions: [],
created_date: 2019-01-02T21:10:04.456Z,
_id: 5c2d28c251f2bd332cdeaf0a,
content: '<p>hehe</p>',
_commented_by: 5c292db763deb43d9434f65f,
_post: 5c2d1dd254ca0429b470f000,
__v: 0 }
So the problem here is that you have two collections, posts and comments. Based on your Posts schema, comments array contains only ids that reference documents that are stored in second collection. That's why you can check whether that array exists and is not empty but you can't refer directly to these elements.
To fix that you can use $lookup to get those documents from comments into posts and then you can apply your date condition inside $match, try:
let posts = await Post.aggregate([
{ $match: { comments: { $exists: true, $ne: [] }, _postedBy: userId } },
{ $lookup: { from: "comments", localField: "comments", foreignField: "_id", as: "comments" } },
{ $match: { 'comments.created_date': { $gte: todayForEvent } } }
])
I'm sorry if this might be a duplicate question but I'm quite having a hard time understanding Mongoose. I am working on a Node.js project that implements Mongoose and MongoDB. What I want to accomplish is to modify and save some users' data through a call from a specific endpoint.
Mongoose Schema looks like this
var UserSchema = new Schema({
isAdmin: {type: Boolean, default: false},
name: String,
surname: String,
nickname: { type: String },
email: { type: String, lowercase: true, required: true, trim: true, unique: true, dropDubs: true },
password: { type: String, required: true },
salt: { type: String },
verified: { type: Boolean, default: false },
bio: {
type: { type: String, enum: [0,1] }, // 0='Appassionato', 1='Giocatore'
birthday: String,
height: Number,
number: Number,
role: { type: String, enum: [0,1,2,3] }, // 0='Playmaker', 1='Ala', 2='Guardia', 3='Centro'
team: String,
city: String,
aboutMe: String,
},
newsletter: {type: Boolean, default: false},
lastCheckin: {type: mongoose.Schema.Types.ObjectId, ref: 'Checkin'},
follows: [{type: mongoose.Schema.Types.ObjectId, ref: 'Structure'}],
score: { type: Number, default: 0 },
profilePicture: String,
lastLogin: {type: Date},
facebook: {
id: String,
accessToken: String,
profileImage : String
}
}, {
collection: 'users',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
Following is the code for when the endpoint gets interrogated
exports.updateUser = (req,res) => {
var userId = req.params.userId;
var updates = req.body;
User.findOneAndUpdate({_id: userId}, {$set: updates}, (err, saved) => {
if (!err) {
console.log("Ritorno questo: " + saved);
return res.status(202).json(saved);
} else {
return res.status(500).json(saved);
}
});
};
As far as I understood, the method findOneAndUpdate exposed by Mongoose should find the document I'm looking for and then modify it and save it. This doesn't happen though.
Through PostMan I'm sending this JSON
{"bio.aboutMe":"Hello this is just a brief description about me"}
But PostMan is responding with the non-modified object. What am I missing here?
What you need to do is to add {new:true}, it give you back the updated document.
In the documentation :
If we do need the document returned in our application there is
another, often better, option:
> Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true },
> function (err, tank) { if (err) return handleError(err);
> res.send(tank); });
This is something I don't really like as there is another option if we don't want to have the document → update
So what you need to do is :
User.findOneAndUpdate({_id: userId}, {$set: updates}, {new:true}.....