Mongoose- Run a function to all Entries in database - node.js

I am a beginner in NodeJs and MongoDB. I have a user schema where I have a field which is an array that is filled by the user's input value. After users enter the value, the admin also passes an array of correct answers. I want to create a function which runs on all users array field and on correct answer store the score in users schema. Just wanted to know how do I run the function on all entries of the collection.
//Final result schema by the admin
const resultSchema = new mongoose.Schema({
matchday:Number,
homeTeam:String,
awayTeam:String,
utcDate:Date,
finalUpdateTime:Date,
result:Array
})
//The predicted answer Schema
const predictSchema = new mongoose.Schema({
user:{
type:mongoose.Schema.ObjectId,
ref:'User',
required:[true, 'Predicted Team must belong to a User']
},
teamData:Array,
matchday: Number,
score:{
type:Number,
default:0
},
createdAt: {
type:Date,
default:Date.now()
},
lastUpdated:Date,
},{
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
})

You can define a static method for your schema. Statics are methods that can be invoked directly by a Model.
See here
You can pass array of correct answers to this method and check the answers for each user in your collection. You can retrieve all users using Find

I managed to solve the issue and it works but not sure if its the correct way to do it
exports.updateUserScore = async (req, res, next) => {
const user = await Predict.find({ matchday: req.body.matchday });
user.map(async (el) => {
let score = 0;
el.teamData.map((e) => {
if (req.body.teamData.includes(e)) score = score + 1;
});
console.log(score, el._id);
await Predict.findByIdAndUpdate(el._id, { score: score });
});
res.status(200).json({
status: 'success',
message: 'Updated User Score Successfully',
});
};

Related

best schema model in my case (mongoose js)

I need advice on the schema.
For now, it looks as shown below:
const accountSchema = new mongoose.Schema({
nickname:{
index:true,
type: String,
},
levels_scores: [Number],
money: Number,
skins: { circles: [Number], sticks: [Number], targets: [Number] },
current_skins: { circles: Number, sticks: Number, targets: Number },
})
my queries are always filtered by nickname so I created index on it.
I very often update money value :
saveMoney(req, res) {
const nickname,money = req.body
Accounts.findOneAndUpdate({ nickname:nickname},{money:money}),{useFindAndModify:false)
res.sendStatus(200)
}
Callback in the case will return all the document data which I don't need.(levels_scores,skins etc.) Performance waste I think
Am I thinking wrong?
Should I make schema with references,like money schema which stores only parent ID and money value?
I also have queries like:
Accounts.findOne({ nickname: req.body.nickname }, (err, account) => {
const part = req.body.skin[0]
const skin_number = req.body.skin[1]
account.skins[part].push(skin_number)
account.markModified("skins")
account.save()
res.sendStatus(200)
})
If i use .select("skins") method to not return all the document data, will I be able to save it?
How would I do that to be the most performant?

Document must have an _id before saving

I know this is an error that has been asked about several times on StackOverflow, but I can't seem to find the answer I'm looking for. I have a simple schema that stores an _id and a URL. The URL works fine, but when I go to create a new schema and save it, it states the above error even though I have prehooks to explicitly define the _id.
Here's the Schema code as well as the prehook:
const LinkSchema = new Schema({
_id: { type: Number },
url: { type: String, required: true }
}, {
timestamps: true,
collection: 'links'
});
LinkSchema.pre('save', function(next) {
// Before saving, increment the count in the linkEntryCount document in the counter collection and create the doc if not already made.
CounterModel.findByIdAndUpdate('linkEntryCount', { $inc: { count: 1 } }, { new: true, upsert: true, useFindAndModify: false }, function(err, counter) {
if(err) return next(err);
this._id = counter.count; // Create the previously undefined ObjectID with the +1'ed counter from linkEntryCount
next();
})
});
I've created an incrementing integer counter as per the MongoDB database - using a separate collection for counting. I've tested this and it works fine, and it even seems to assign the _id when the prehook is called. When I create an instance of the model and insert the URL, that's when the error appears. The document isn't even created.
Thanks for your help!
_id: { type: Number },
A mongodb _id isnt a number but a mongoose.Schema.Types.ObjectId
So replace that line with
_id: { type: mongoose.Schema.Types.ObjectId },
You should use _id only with
new mongoose.Types.ObjectId()
And then you can add like id: { type: Number } and use that as the counter
So I solved my question using this post.
The problem was that this was being used in the wrong context inside the CounterModel.findByIdAndUpdate() function. The code was trying to update a nonexistent field in the linkEntryCount collection.
Here's the fixed prehook:
LinkSchema.pre('save', function(next) {
var link = this;
// Before saving, increment the count in the linkEntryCount document in the counter collection and create the doc if not already made.
CounterModel.findByIdAndUpdate('linkEntryCount', { $inc: { count: 1 } }, { new: true, upsert: true, useFindAndModify: false }, function(err, counter) {
if(err) return next(err);
link._id = counter.count; // Create the previously undefined ObjectID with the +1'ed counter from linkEntryCount
next();
});
});
All I did was set the prehooks reference to this to a variable and then used that later in the findByIdAndUpdate() function.

Mongoose schema for article

I'm building a news website, and I this mongoose schema:
let mongoose = require('mongoose');
let articleSchema = mongoose.Schema({
image1:{
type: String,
required: true
},
title:{
type: String,
required: true
},
author:{
type: String,
required: true
},
date:{
type: String,
required: true
},
updated:{
type: String,
default: 'not updated'
},
title_nd:{
type: String,
required: false
},
body:{
type: String,
required: true
},
comments: [commentsSchema],
likes:{ type:Number, default:0 }
});
let Article = module.exports = mongoose.model('Article', articleSchema);
And I want to add a form so users can add their comments.
The question is how do I create a new schema for comments and link it to article schema, and then if the user adds a comment the comment added to the database and then shows on the article comment section?
Modeling a separate schema for comment is not a good idea in my humble opinion, since it is a classic case of one to few mapping which is an ideal use case for embedding the document. To give you a basic idea about data modeling i am quoting here
You need to consider two factors:
Will the entities on the ā€œNā€ side of the One-to-N ever need to stand alone?
What is the cardinality of the relationship: is it one-to-few; one-to-many; or one-to-squillions?
Based on these factors, you can pick one of the three basic One-to-N schema designs:
Embed the N side if the cardinality is one-to-few and there is no need to access the embedded object outside the context of the parent object
Use an array of references to the N-side objects if the cardinality is one-to-many or if the N-side objects should stand alone for any reasons
Use a reference to the One-side in the N-side objects if the cardinality is one-to-squillions
Please refer to a very well written and articulated post 6 Rules of Thumb for MongoDB Schema Design: Part 1 from mongodb blogs.
Even after this if you think it is a good idea to link to another schema please refer to this SO question - Referencing another schema in Mongoose
so I found a solution for this:
// :id is all articles with all ids
router.post('/:id', function (req, res) {
let comment = {};
comment.body = req.body.body;
comment.user = req.user;
comment.date = new Date(Date.now()).toDateString();
// Express validator
req.checkBody('body').len(5, 100);
let errors = [];
errors = req.validationErrors();
if(errors) {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
res.redirect('/articles/'+article.id);
});
} else {
Article.findById(req.params.id, function (err, article) {
if(err)throw err;
article.comments.push({'body':comment.body,'user':comment.user,'date':comment.date});
article.save(function (err) {
if (err) {
throw err;
}else {
req.flash('success', 'Comment added!');
res.redirect('/articles/'+article.id);
}
});
});
}
});
EDIT: code above in more readable form:
router.post('/:id', async (req, res) => {
let article = await Article.findById(req.params.id);
if (!article) res.status("403");
let articleUrl = "/articles/${article.id}";
let comment = {
body: req.body.body,
user: req.user,
date: new Date(Date.now()).toDateString();
};
if (commment.body.lengh >= 100 || comment.body.length <= 5) {
req.flash('danger', 'Body minimum length is 5 and maximum 100!');
return res.redirect(articleUrl);
}
articles.comments.push(comment);
await article.save();
req.flash('success', 'Comment added!');
res.redirect(articleUrl);
});

Saving data to array in mongoose

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

Save two referenced documents simultaneously

I've got an stock application where I want to set some details about the stock and then insert all the items of the stock. I want to insert the stock details and the items in two different collection so then I can filter the items. I'm using the MEAN Stack where I've modified the crud module to accept some extra fields and also made the UI for filling the items array.This what I have so far:
scope.stockItems = [];
$scope.createStockItem = function () {
$scope.stockItems.push(
{
brand: $scope.brand,
style: $scope.style,
amount: $scope.amount
}
);
$scope.brand = false;
$scope.style = false;
$scope.amount = '';
};
// Create new Stock
$scope.create = function() {
// Create new Stock object
var stock = new Stocks ({
name: this.name,
details: this.details,
stockDate: this.stockDate
});
// Redirect after save
stock.$save(function(response) {
$location.path('stocks/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
The stock model:
var StockSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Stock name',
trim: true
},
details: {
type: String,
default: '',
required: 'Please fill Stock details'
},
stockDate: Date
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
and the method in the server controller:
exports.create = function(req, res) {
var stock = new Stock(req.body);
stock.user = req.user;
stock.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(stock);
}
});
};
How can I send into the request and save the stockItems also?
By saying 'simultaneously' I think you are requiring transaction feature, which is really an RDBMS thing, and is not supported by MongoDB. If your application strongly relies on such features, I'm afraid MongoDB is not the right choice for you.
So back to your question, I don't understand why you have to store stock and stock item in 2 different collections. Store them in one collection would be a better choice. You can refer to the Data Model Design of MongoDB Manual for more information. If it's just to filter all the stock items, aggregation framework is designed for such purpose. As well as Map/Reduce. Here aggregation framework suits better for your issue. You would have something like:
db.stock.aggregate([
{$match: {...}}, // same as find criteria. to narrow down data range
{$unwind: "$items"}, // unwind items.
... // probably some other $match to filter the items
]);

Resources