Update field using previous value (mongodb) [duplicate] - mongodb

Is it possible, using mongoose middleware, to increment two fields one with a condition and the other without? In this case i want to increment "stats.ratings" by one, if the user inserts an input greater than 0, else increment zero.
"stats.answered" always increments one
See code below
module.exports.updateStats = function (req, res) {
var rating = parseInt(req.body.rating, 10);
var wasRated;
if (rating > 0) {
wasRated = true;
} else wasRated = false
Collection.findOneAndUpdate({
_id: req.body._id
}, {
$cond: {
if: wasRated,
then: {
$inc: {
"stats.answered": 1,
"stats.ratings": 1
}
},
else: {
$inc: {
"stats.answered": 1,
"stats.ratings": 0
}
}
}
},
function (err, doc) {
if (err)
throw err;
res.status(200);
})
}

What you can do is this:
// define the default case
var update = {
$inc: {
"stats.answered": 1
}
};
if(parseInt(req.body.rating, 10) > 0) {
// override default in some cases
update = {
$inc: {
"stats.answered": 1,
"stats.ratings": 1
}
}
}
and then
Collection.findOneAndUpdate({
_id: req.body._id
}, update,
function (err, doc) {
if (err)
throw err;
res.status(200);
})
}

Related

How can I pass a variable in sort funtcion of mongobd?

I want to pass this name variable in sort function. I am getting the value of name in console.log but its not working in sort function.
var coldata = req.body.order[0].column;
var name = req.body.columns[coldata].data; // Want to pass this variable in sort()
var first = req.body.order[0].dir;
var last = req.body.order[0].dir;
var x,y;
if (first == 'asc'){
x = 1
}else{
x = -1;
}
if (last == 'asc'){
y = 1
}else{
y = -1;
}
var searchStr = req.body.search.value;
if(req.body.search.value)
{
var regex = new RegExp(req.body.search.value, "i")
searchStr = { $or: [{'firstname':regex },{'lastname': regex}] };
}
else
{
searchStr={};
}
console.log(req.body.search.value)
var recordsTotal = 0;
var recordsFiltered=0;
console.log(searchStr);
db.count({}, function(err, c) {
recordsTotal=c;
db.count(searchStr, function(err, c) {
recordsFiltered=c;
db.find(searchStr, 'firstname lastname',{'skip': Number( req.body.start), 'limit': Number(req.body.length) }, function (err, results) {
if (err) {
console.log('error while getting results'+err);
return;
}
var data = JSON.stringify({
"draw": req.body.draw,
"recordsFiltered": recordsFiltered,
"recordsTotal": recordsTotal,
"data": results
});
res.send(data);
}).sort({name:x});// Not getting value of name here
});
});
});
You can use an aggregation pipeline
const sort = {};
sort[name] = x
const pipeline = [
{ $match: searchStr },
{ $skip: Number( req.body.start) },
{ $limit: Number( req.body.length) },
{ $sort: sort }
];
db.aggregate(pipeline) ...

I am learning express and mongoose and I cannot get a schema to save, but can see it with console.log()

EDIT: I think weekCount is saving before the records are loaded into it. Perhaps incorrect use of the find()?
I have started a website to record animals in camps (paddocks). I am using express, mongo, mongoose, and pug.
I have a schema (see below) that has an array of type: Schema.Types.ObjectId that I can not save to mongo. The array is empty.
I have a form that fetches the camp names, and the animal types (cows, bulls, calves) and creates a form.
The form is displayed, and the POST can read the form data. The form data is read into schema and is displayed ok with console.log, but does not save the entire schema in mongo.
I have read a lot of posts, and tried many things such as markModified, used a full Schema.
Its been hours now, and I would appreciate some help.
Express mongoose Models:
var WeekCountSchema = new Schema({
dateOfCount: { type: Date },
campCounts: [{type: Schema.Types.ObjectId, ref: 'CampCount'}] < ----- problem
});
var CampCountSchema = new Schema({
campName: String,
campCountDate: Date,
count: {
ox: Number,
cow: Number,
bull: Number,
heifer: Number,
calf: Number,
weaner: Number
}
});
weekCountController:
Scroll Down to the <----
exports.weekCount_create_post = [
validator
.body("dateOfCount", "Week Count Data Required")
.toDate()
.isLength({ min: 1 })
.trim(),
validator.sanitizeBody("*").escape(),
(req, res, next) => {
var weekCount = new WeekCount({
dateOfCount: req.body.dateOfCount
});
const errors = validator.validationResult(req);
if (!errors.isEmpty()) {
// There are errors. Render the form again with sanitized values/error messages.
console.log("ERRORS!s");
async.parallel(
{
camps: function(callback) {
Camp.find()
.sort("name")
.exec(callback);
},
cattleClasses: function(callback) {
CattleClass.find(callback);
}
},
function(err, results) {
if (err) {
return next(err);
}
res.render("weekCount_form", {
title: "There were Errors! New Week Count",
camps: results.camps,
cattleClasses: results.cattleClasses
});
}
);
return;
} else {
// Data from form is valid.
Camp.find({}, "name").exec(function(err, list_camps) {
if (err) {
return next(err);
}
CattleClass.find({}, "name").exec(function(err, list_cattleClasses) {
if (err) {
return next(err);
}
var campCountArray = [];
list_camps.forEach(function(campName) {
var campCount = new CampCount({
campName: campName.name
});
var emptyCount = true;
list_cattleClasses.forEach(function(cattleClassName) {
var tempVar = campName.name + "." + cattleClassName.name;
var tempNum = parseInt(req.body[tempVar]);
// console.log(tempNum);
if (tempNum) {
// console.log(req.body[tempVar]);
campCount.count[cattleClassName.name] = req.body[tempVar];
emptyCount = false;
} else {
campCount.count[cattleClassName.name] = 0;
}
});
if (!emptyCount) {
campCount.save(function(err) {
if (err) {
return next(err);
}
});
campCountArray.push(campCount);
}
});
console.log("CampCountArray");
console.log(campCountArray);
weekCount.campCounts = campCountArray;
});
});
// ****************************************************************
// Check if Week Count with same date already exists.
WeekCount.findOne({ dateOfCount: req.body.dateOfCount }).exec(function(
err,
found_weekCount
) {
if (err) {
console.log("ERROR findone " + err);
return next(err);
}
if (found_weekCount) {
// Week count exists, redirect to its detail page.
console.log("FOUND");
res.redirect(found_weekCount.url);
} else {
console.log("NOT FOUND");
// weekCount.markModified('campCounts');
weekCount.save(function(err) { // <---- does not save
if (err) {
console.log("ERROR SAVING: " + err);
return next(err);
}
console.log("weekCount saved");
console.log(weekCount);
// output below
// Week Count saved. Redirect to week count detail page.
// console.log(weekCount.campCounts);
res.redirect(weekCount.url);
});
}
});
}
}
];
Output from console.log:
GET /catalog/WeekCount/create 200 219.085 ms - 3782
NOT FOUND <------ count not a duplicate (OK)
CampCountArray
[ { count: { calf: 1, bull: 0, cow: 0, weaner: 0, ox: 0, heifer: 0 },
_id: 5d83720e2279011e90a1614b,
campName: 'Bloekom' },
{ count: { calf: 1, bull: 0, cow: 0, weaner: 0, ox: 0, heifer: 0 },
_id: 5d83720e2279011e90a1614c,
campName: 'Davel' },
{ count: { calf: 1, bull: 0, cow: 0, weaner: 0, ox: 0, heifer: 0 },
_id: 5d83720e2279011e90a1614d,
campName: 'Elfas' },
{ count: { calf: 1, bull: 0, cow: 0, weaner: 0, ox: 0, heifer: 0 },
_id: 5d83720e2279011e90a1614e,
campName: 'Groot kamp' } ]
weekCount saved
{ campCounts:
[ { count: [Object],
_id: 5d83720e2279011e90a1614b,
campName: 'Bloekom',
__v: 0 },
{ count: [Object],
_id: 5d83720e2279011e90a1614c,
campName: 'Davel',
__v: 0 },
{ count: [Object],
_id: 5d83720e2279011e90a1614d,
campName: 'Elfas',
__v: 0 },
{ count: [Object],
_id: 5d83720e2279011e90a1614e,
campName: 'Groot kamp',
__v: 0 } ],
_id: 5d83720e2279011e90a1614a,
dateOfCount: 2019-09-06T00:00:00.000Z,
__v: 0 }
from mongo:
{
"_id" : ObjectId("5d83720e2279011e90a1614a"),
"campCounts" : [ ], <---------------------- nothing here!
"dateOfCount" : ISODate("2019-09-06T00:00:00Z"),
"__v" : 0
}
the campCounts ARE in mongo (sample):
{
"_id" : ObjectId("5d83720e2279011e90a1614d"),
"count" : {
"calf" : 1,
"bull" : 0,
"cow" : 0,
"weaner" : 0,
"ox" : 0,
"heifer" : 0
},
"campName" : "Elfas",
"__v" : 0
}
but weekCount does not update. This is the problem.
I think your suspicion is correct that weekCount is saving before the CattleClass.find() callback has finished executing.
You could fix this with .then syntax:
CattleClass.find({}, "name").exec(function(err, list_cattleClasses) {
...
}).then( function () {
WeekCount.findOne({ dateOfCount: req.body.dateOfCount }).exec(function(
err, found_weekCount) {
...
}
})
you could also use ES6 async/await syntax, but your code would take quite a bit of rewriting because you can't use .exec() with await.
Both methods will ensure CattleClass.find() finishes executing before running WeekCount.findOne()
If you need more info there are lots of great posts on Stack Overflow addressing asynchronous code.
The below code works properly now.
Use of .then() instead of mixing callbacks and promises.
Thanks for the help!
exports.weekCount_create_post = [
validator
.body("dateOfCount", "Week Count Data Required")
.toDate()
.isLength({ min: 1 })
.trim(),
// Sanitize (escape) the name field.
validator.sanitizeBody("*").escape(),
// Process request after validation and sanitization.
(req, res, next) => {
var weekCountDetail = {
dateOfCount: req.body.dateOfCount
};
const errors = validator.validationResult(req);
if (!errors.isEmpty()) {
// There are errors. Render the form again with sanitized values/error messages.
console.log("ERRORS!s");
async.parallel(
{
camps: function(callback) {
Camp.find()
.sort("name")
.exec(callback);
},
cattleClasses: function(callback) {
CattleClass.find(callback);
}
},
function(err, results) {
if (err) {
return next(err);
}
res.render("weekCount_form", {
title: "There were Errors! New Week Count",
camps: results.camps,
cattleClasses: results.cattleClasses
});
}
);
return;
} else {
Camp.find({}, "name")
.exec()
.then(list_camps => {
return CattleClass.find({}, "name")
.exec()
.then(list_cattleClasses => {
return [list_camps, list_cattleClasses];
});
})
.then(qResult => {
list_camps = qResult[0];
list_cattleClasses = qResult[1];
var campCountArray = [];
list_camps.forEach(function(campName) {
var campCount = new CampCount({
campName: campName.name
});
var emptyCount = true;
list_cattleClasses.forEach(function(cattleClassName) {
var tempVar = campName.name + "." + cattleClassName.name;
var tempNum = parseInt(req.body[tempVar]);
if (tempNum) {
campCount.count[cattleClassName.name] = req.body[tempVar];
emptyCount = false;
} else {
campCount.count[cattleClassName.name] = 0;
}
});
if (!emptyCount) {
campCount.save(function(err) {
if (err) {
return next(err);
}
});
campCountArray.push(campCount);
}
});
weekCountDetail.campCounts = campCountArray;
return weekCountDetail;
})
.then(weekCountDetail => {
WeekCount.findOne({ dateOfCount: req.body.dateOfCount })
.exec()
.then(found_weekCount => {
if (found_weekCount) {
res.redirect(found_weekCount.url);
} else {
console.log("Not FOUND");
var weekCount = new WeekCount(weekCountDetail);
console.log("WEEKCOUNT3");
console.log(weekCount);
weekCount.save(err => {
if (err) {
return next(err);
}
res.redirect(weekCount.url);
});
}
})
.catch(err => {
console.log("error findOne " + err);
return next(err);
});
});
}
}
];

Mongoose loop through findOneAndUpdate condition statement

I want to know if this part of code can be written differently, only with Mongoose helper methods of models ? Can I return a success and error if no stock are greater then 0 ?
ProductSchema.statics.substractStock = function (products) {
_.map(products, updateStock)
function updateStock(o) {
mongoose.model('Product').findById(o._id, function (err, product) {
return product
}).then(function(productDB){
if(productDB.stock > o.stock && productDB.stock > 0){
mongoose.model('Product').findOneAndUpdate(o._id, {$inc: {stock: -(o.stock)}}, {},
function (err, doc) {
//return success ??
}
);
} else {
//return 'no update'
}
});
}
};
This could be done with an atomic update where you can ditch the initial findById() call and include the comparison logic
if (productDB.stock > o.stock && productDB.stock > 0) { ... }
within the query as in the following:
function updateStock(o) {
mongoose.model('Product').findOneAndUpdate(
{
"_id": o._id,
"$and": [
{ "stock": { "$gt": o.stock } } ,
{ "stock": { "$gt": 0 } }
]
},
{ "$inc": { "stock": -(o.stock) } },
{ "new": true }, // <-- returns modified document
function (err, doc) {
// check whether there was an update
}
);
}

With a mongoose .update query, can you refer to the fetched record in the update parameter?

Is it possible to something like this with Mongoose or even just Mongo?
Center.update({ghostBuster:{$exists}},{$set:{ectoplasm: this.exoplasm},$unset: {exoplasm:""}}, function(err, result){ })
I would like to update a number of records and move one field into another field, so if I could refer to that fetched record as I update it. In this case I'm making the ectoplasm field have the value of exoplasm
Is it possible to do this without defining a hook on the Mongoose Schema?
With mongo you can do this by iterating the cursor returned from the find query (using forEach() method) and update the collection within the loop. For example:
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc){
db.centers.update(
{ "_id": doc._id },
{
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
)
});
This "back and forth" to the server is going to cost in IO, so you would try to minimize it. Use bulkWrite() method (if using MongoDB version 3.2) to do the updates in bulk:
var ops = [];
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc) {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
}
});
if (ops.length === 1000) {
db.centers.bulkWrite(ops);
ops = [];
}
})
if (ops.length > 0) db.centers.bulkWrite(ops);
Or for MongoDB 2.6.x and 3.0.x releases use this version of Bulk operations:
var bulk = db.centers.initializeUnorderedBulkOp(),
counter = 0;
db.centers.find({
"ghostBuster": { "$exists": true }
}).forEach(function(doc) {
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
});
if (counter % 1000 === 0) {
bulk.execute();
bulk = db.centers.initializeUnorderedBulkOp();
}
});
if (counter % 1000 !== 0 ) bulk.execute();
The Bulk operations API in both cases will help reduce the IO load on the server by sending the requests only once in every 1000 documents in the collection to process.
For the Mongoose equivalent, you can implement something like the following which uses Promises to handle the async nature of the bulk API in node.js.
In order to use the underlying bulk operations API, you should access it via the .collection property from the mongoose model. Before using the API, wait for mongoose to successfully connect to the db since Mongoose doesn't really support the "initializeOrderedBulkOp()" function yet, because it doesn't work with mongoose's internal buffering system.
var mongoose = require('mongoose'),
express = require('express'),
Promise = require('bluebird'),
Schema = mongoose.Schema;
function connect(uri, options){
return new Promise(function(resolve, reject){
mongoose.connect(uri, options, function(err){
if (err) return reject(err);
resolve(mongoose.connection);
});
});
}
var centerSchema = new Schema({
exoplasm: Number,
ghostBuster: Number,
time: Date
}, { collection: "centers" });
var Center = mongoose.model("Center", centerSchema);
/*
function bulkUpdate(Model){
return new Promise(function(resolve, reject){
var bulk = Model.collection.initializeUnorderedBulkOp(),
counter = 0;
Model.find({ "ghostBuster" : { "$exists": true } })
.lean().exec(function (err, docs) {
if (err) return reject(err);
docs.forEach(function (doc){
counter++;
bulk.find({ "_id": doc._id }).updateOne({
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
});
if (counter % 500 == 0 ) {
bulk.execute(function(err, result) {
if (err) return reject(err);
bulk = Model.collection.initializeUnorderedBulkOp();
resolve(result);
});
}
});
if (counter % 500 != 0 ) {
bulkUpdateOps.execute(function(err, result) {
if (err) return reject(err);
resolve(result);
});
}
});
});
}
*/
function bulkUpdate(Model){
return new Promise(function(resolve, reject){
var ops = [],
collection = Model.collection;
Model.find({ "ghostBuster" : { "$exists": true } })
.lean().exec(function (err, docs) {
if (err) return reject(err);
docs.forEach(function (doc){
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": {
"$set": { "ectoplasm": doc.exoplasm },
"$unset": { "exoplasm": "" }
}
}
});
if (ops.length === 1000) {
collection.bulkWrite(ops, function(err, result) {
if (err) return reject(err);
ops = [];
resolve(result);
});
}
});
if (ops.length > 0) {
collection.bulkWrite(ops, function(err, result) {
if (err) return reject(err);
resolve(result);
});
}
});
});
}
connect('mongodb://localhost/test', {}).then(function(db){
bulkUpdate(Center).then(function(res){
console.log('Bulk update complete.', res);
}, function(err){
console.log('Bulk Error:', err);
db.close();
});
}, function(err){
console.log('DB Error:', err);
});

Meteor mongo get this value

I have a meteor game
On the server i have a timer that calls meteor method moveFish.
Meteor.startup(() => {
Meteor.setInterval(function(){
Meteor.call("moveFish")
}, 40);
});
That method selects all fishes are alive and make them move
Meteor.methods({
moveFish: function(id, speed) {
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": 2 } } )
}
})
How do I move fish using this.fish.speed instead value 2
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": 2 } } )
*Notice that doesn't work
Meteor.users.update( { "fish.alive": true }, { $inc: { "fish.positionX": "fish.speed" } } )
That's works
Meteor.users.find().map( function(user) { x = user.fish.speed Meteor.users.update(user, {$inc: {"fish.positionX": x} }) })
Unfortunately document can't use the reference to itself on update operation.
You need to find it first and iterate over all documents manually in this case:
Meteor.users.findAll({ "fish.alive": true }).fetch().forEach(function(fish) {
fish.positionX += fish.speed;
fish.save();
});