mongodb update not working to replace a document (but $set does) - mongodb

From homework 2_2 of the M101JS course at university.mongodb.com.
Write a program that finds the document with the highest recorded temperature for each state, and adds a "month_high" field for that document, setting its value to true.
The following code should work to update the correct doc in the collection adding "month_high". And the problem is here:
//db.collection('data').update(query, setMonthHigh, function(err, updated) {//OK
db.collection('data').update(query, doc, function(err, updated) {// NOT OK, dunno why
The 2nd update line does not save anything. But if I use the commented update above, it works.
Does anyone have an idea why the 2nd update line isn't working (the one that replaces the whole doc)?
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/weather', function(err, db) {
if(err) throw err;
var options = { 'sort' : [ ['State', 1], ['Temperature', -1] ]};
var lastDoc = null;
var setMonthHigh = {'$set':{'month_high':true}};
var cur = db.collection('data').find({}, {},options);
//var cur = db.collection('data').find({'month_high':true}, {},options);
cur.each(function(err, doc) {
if(err) throw err;
if(doc == null) {
console.log("Document done");
//return db.close();
db.close();
process.exit(0);
}
var query = {'_id' : doc._id};
//db.collection('data').update({'month_high':true}, {$unset:{'month_high':1}}, function(err, updated) {});
//console.dir(doc);
if ( ( lastDoc != null && lastDoc.State != doc.State ) ||
lastDoc == null )
{
doc['month_high'] = 'true';
//db.collection('data').update(query, setMonthHigh, function(err, updated) {//OK
db.collection('data').update(query, doc, function(err, updated) {
// NOT OK, dunno why
if(err) throw err;
console.dir("Update "+updated);
});
console.dir(doc);
}
lastDoc = doc;
});
});
Also, I'm told that using process.exit(0) is a bad hack, but it was the only way I could exit the code without throwing exceptions on return db.close();. If anyone knows a way to exit the code gracefully, that would be great too.

First of all, you have made a mistake in part of your code responsible for updating a database.
db.collection('data').update is an asynchronous function. It means that you cannot execute db.close() without knowing if all updates have been completed. For more details please read: How can I use a cursor.forEach() in MongoDB using Node.js?
Both updates works. However there is one difference between two of them:
db.collection('data').update(query, setMonthHigh, function(err, updated)
updates records with a new field: 'month_high': true <- boolean value
Query to find these records is: db.data.find({'month_high': true})
db.collection('data').update(query, doc, function(err, updated)
updates records with a new field: 'month_high': 'true' <- string value
Query to find these records is: db.data.find({'month_high': 'true'})
Your updated code:
Before run it, first install async library (npm install async)
var async = require('async');
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/weather', function(err, db) {
if(err) throw err;
var q = async.queue(function (doc, callback) {
db.collection('data').update({_id: doc._id}, doc, callback);
}, Infinity);
var options = { 'sort' : [ ['State', 1], ['Temperature', -1] ]};
var lastDoc = null;
var cur = db.collection('data').find({}, {},options);
cur.each(function(err, doc) {
if(err) throw err;
if(doc === null) return;
if ( ( lastDoc !== null && lastDoc.State !== doc.State ) ||
lastDoc === null )
{
doc['month_high'] = true;
q.push(doc, function(err, updated) {
if(err) throw err;
console.log("Update "+updated);
});
}
lastDoc = doc;
});
q.drain = function() {
if (cur.isClosed()) {
console.log('Document done');
db.close();
}
}
});

Related

Order of save() and find() in NodeJS with MongoDB

I'm trying to create a new record in my MongoDB ("thisPlayer") and save it to my database, then find all records in my database (including the new one) and render them.
I am having trouble understanding why my save() function actually occurs after my find() function. When this code executes, the find() function does not include my new thisPlayer record. However, after the find() runs, the save occurs -- the record is saved to the database AFTER the find() ran.
Thanks in advance!
const playerNumber = async function countPlayers() {
return new Promise((resolve, reject) => {
Player.count(function(err, numOfDocs) {
err ? reject(err) : resolve(numOfDocs);
console.log('I have '+numOfDocs+' documents in my collection');
});
});
}
async function playerProfile() {
var count = await playerNumber();
console.log("count already in db: "+ count);
if (count===0) {
teamCaptain=1;
} else {teamCaptain=0};
count++;
const thisPlayer = new Player({
playerNum: count,
playerName: Name,
});
thisPlayer.save();
Player.find({}, function(err, playaz){
var playerOne;
if (playaz.length > 0) {
playerOne = playaz[0].playerName;
} else {
playerOne = "";
}
res.renderPjax("leavetakings",
{player1: "1: " + playerOne}
);
});
}
playerProfile();
You need to use await
for example.
await Player.find({})

How to stop inserting duplicate records before saving in db?

I'm trying to save students records, but it should not take duplicate records. How is it possible? In below code i have tried to do
app.post("/save",function(req,res){
var std=new student(req.body);
student.findOne({},function(err,success){
if(err)
{
console.log(err);
}
else
{
// console.log(success);
std.save(function(err,success){
if(err)
{
console.log(err);
}
else
{
console.log("inserted");
console.log(success);
}
});
}
})
});
Here is the sample code. Please note that the existence of the value in MongoDB database depends on the req.body as mentioned in the OP.
In the below code, I have only name attribute in the Student collection. So, the duplicate check is based on the name attribute only.
You may need to change the code if you would like to check for the specific attribute in the collection to determine the duplicate value.
Please note that my Student collection has only attribute in the schema as well.
var express = require('express');
var MongoClient = require('mongodb').MongoClient;
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var Student = mongoose.model('Student', { name: String });
var app = express();
var bodyParser = require('body-parser');
var app = express();
var urlencoded_body_parser = bodyParser.urlencoded({
extended: true
});
app.use(bodyParser.json());
app.use(urlencoded_body_parser);
app.post("/save", function (req, res) {
console.log(req.body);
var student = new Student(req.body);
Student.findOne(req.body, function (err, success) {
if (err) {
console.log(err);
res.send(err);
}
else {
console.log(success);
if (success == null) {
student.save(function (err, success) {
if (err) {
console.log(err);
res.send(err);
}
else {
console.log("inserted");
console.log(success);
res.send("success");
}
});
} else {
res.send("Student already present");
}
}
})
});
app.listen(3000);
Output:-
First time execution:-
Input:-
{
"name" : "john"
}
Output:-
success
Subsequent executions with the same input json:-
Output:-
Student already present

Waiting for meteor cursor in method

I have a large aggrogate query that required me to pass "allowDiskUse: true" as an option. This would not work with the aggegate as described here:
https://github.com/meteorhacks/meteor-aggregate/issues/11
My meteor method is defined here. When I call the method I need to wait for ondata to complete before anything is returned to the client, but nothing I try allows me to get that data in a safe way up to the front end.
Meteor.methods({
'getSummary': function (dept,startDate,endDate,filterType) {
f = myQuery(startdate,enddate,dayFinalGroup);
f.on("data", Meteor.bindEnvironment(function(row) {
//load an array or something here to return
}));
f.once("end", Meteor.bindEnvironment(function() {
// tidy up, in my case end the stream
}));
//here I'd return the array loaded
},
});
This is my front end.
Meteor.call(
'getSummary',0,Session.get('start_date'),Session.get('end_date'),1,
function(error, result){
if(error){
console.log(error);
} else {
Session.set('sumTotals',result);
}
}
);
Finally Got it. I utilized wrapSync
'getSummary': function (dept,startDate,endDate,filterType) {
console.log(dept);
console.log(startDate);
console.log(endDate);
console.log(filterType);
var startdate = new Date(startDate);
var enddate = new Date(endDate);
var arr = [];
f = myQuery(startdate,enddate,dayFinalGroup);
var fetchCursor = Meteor.wrapAsync(function fetchCursor (cursor, cb) {
cursor.each(function (err, doc) {
if (err) return cb(err);
if (!doc) return cb(null, { done: true }); // no more documents
arr.push(doc);
});
});
var myData = fetchCursor(f);
return arr;

How do I send a Mongo Document back to the front end?

//router
app.get('/retrieve_report', function(req, res) {
var retrieved = retrieve_report(req, res);
res.render('retrieve_report.ejs', {
'report' : retrieved
});
});
//Load up the report model
var Report = require('../models/report');
console.log('Report ' + Report.schema);
//expose this function to our app using module.exports
//query
module.exports = function(req, res) {
//console.log('param ' + res.send);
var query = Report.findById(req.param('id'), function(err, doc) {
if(err) {
throw err;
}
else {
console.log('doc ' + JSON.stringify(doc));
res.send(doc);
}
});
}
//app.js
var retrieve_report = require('./config/retrieve_report');//which is the above code
I want to return the document to the router so that I can put its information into my view. I tried "res.json(doc), but that gave me the error, "throw new Error('Can\'t set headers after they are sent.');" Everyone says to use a callback function, but aren't I using a callback function here?
As your error says:
but that gave me the error, "throw new Error('Can\'t set headers after they are sent.');"
Means you are trying to send data the twice.
Sample code:
app.get('/retrieve_report', function(req, res) {
var query = Report.findById(req.param('id'), function(err, doc) {
if(err) {
throw err;
}
else {
console.log('doc ' + JSON.stringify(doc));
res.send(doc);
}
});
This should work..

Mongodb inserts not completing successfully (using node.js)

I've got a node.js script that loads an XML file. It loops through each element in the Mongo array and says that they're all getting inserted correctly, but when the script has completed a check of db.collection.count(); tells me that far fewer records have been inserted into the database than the number expected.
How can I make mongo and node.js play nicely with inserts?
GrabRss = function() {
var http = require('http');
var sys = require('sys');
var xml2js = require('xml2js');
var fs = require('fs');
var Db = require('../lib/mongodb').Db,
Conn = require('../lib/mongodb').Connection,
Server = require('../lib/mongodb').Server,
// BSON = require('../lib/mongodb').BSONPure;
BSON = require('../lib/mongodb').BSONNative;
var data;
var checked = 0;
var len = 0;
GotResponse = function(res) {
var ResponseBody = "";
res.on('data', DoChunk);
res.on('end', EndResponse);
function DoChunk(chunk){
ResponseBody += chunk;
}
function EndResponse() {
//console.log(ResponseBody);
var parser = new xml2js.Parser();
parser.addListener('end', GotRSSObject);
parser.parseString(ResponseBody);
}
}
GotError = function(e) {
console.log("Got error: " + e.message);
}
GotRSSObject = function(r){
items = r.item;
//console.log(sys.inspect(r));
var db = new Db('myrssdb', new Server('localhost', 27017, {}), {native_parser:false});
db.open(function(err, db){
db.collection('items', function(err, col) {
len = movies.length;
for (i in items) {
SaveItem(items[i], col);
}
});
});
}
SaveItem = function(m, c) {
/* REPLACE FROM HERE IN ANSWER */
c.find({'id': m.id}, function(err, cursor){
cursor.nextObject(function(err, doc) {
if (doc == null) {
c.insert(m, function(err, docs) {
docs.forEach(function(doc) {
console.log('Saved: '+doc.id+' '+doc.keywords);
});
});
} else {
console.log('Skipped: '+m.id);
}
if (++checked >= len) {
process.exit(0);
}
});
});
/* REPLACE TO HERE IN ANSWER */
}
//http.get(options, GotResponse).on('error', GotError);
var x2js = new xml2js.Parser();
fs.readFile('/home/ubuntu/myrss.rss', function(err, data) {
x2js.parseString(data);
});
x2js.addListener('end', GotRSSObject);
}
GrabRss();
As requested, the code is above. The file is read locally (though is used to be an HTTP request, but it's a 25 meg file now, lots of RSS records)
I just ran the file with some ~10k records in it and a count of the items in the mongoDB after the script has run is about 800 items.
As per the answer I replaced the insert code:
with:
c.update({'id': m.id}, {$set: m}, {upsert: true, safe: true}, function(err){
if (err) console.warn(err.message);
else console.log(m.keywords);
if (++checked >= len) {
console.log(len);
//process.exit(0);
process.exit(0);
}
});
By default, MongoDB writes do not check for an error.
You need to set safe:true in the options to your insert, as explained in the documentation for node-mongodb-native:
var collection = new mongodb.Collection(client, 'test_collection');
collection.insert({hello: 'world'}, {safe:true},
function(err, objects) {
if (err) console.warn(err.message);
if (err && err.message.indexOf('E11000 ') !== -1) {
// this _id was already inserted in the database
}
});
Otherwise your callback will not be invoked for errors and your client won't know about them.
You probably also want to look at upserts and updates, as it is incredibly inefficient to do find & insert if null in a loop.
Instead, upsert will update if the matching document exists, otherwise it will insert a new one. An explanation on how to do this in Node is in the documentaiton for the driver.