I am doing an online course about MongoDB which is unfortunately a little out of date. It seems some of the functions have changed (course is using version 1.4 while I am using 3.0.)
Here is the code I am having trouble with, which I have tried to bring up to date with the current version of MongoDB:
app.js
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://localhost:27017/course', function(err, db) {
if (err) throw err;
db.collection['counters'].findAndModify({
query: {
name: 'comments'
},
update: {
$inc: {
counter: 1
}
},
new: true
}, function(err, doc) {
if (err) throw err;
if (!doc) {
console.dir('No counter found for comments.');
} else {
console.dir('Number of comments: ' + doc.counter);
}
return db.close();
});
});
If I run the same findAndModify through the Mongo shell I get the anticipated result (increment the counter and display the new document,) but when I run this with node it has no effect on the database and throws this error:
TypeError: Cannot call method 'findAndModify' of undefined
Any tips?
Please try:
db.counters('counters').findAndModify
instead of:
db.collection['counters'].findAndModify
use this now:
db.collection('counters').findOneAndUpdate(
{name: 'comments'}, //query
{$inc: {counter: 1}}, //update
{ //options
upsert: true, // create the doc when it's not there
returnOriginal:false // return the modified doc *(new is not supported here!)
},
function(err, r){ //callback
if(err) throw err;
console.log('counter: '+r.value.counter);
}
);
Whoops, I just had the wrong kind of brackets. Should have had:
db.collection('counters')
instead of
db.collection['counters']
Almost like T_G said.
From the mongodb docs:
Existing collections can be opened with collection
db.collection([[name[, options]], callback);
If strict mode is off, then a new collection is created if not already
present.
So you need to do this:
db.collection('counters', function(err, collection){
collection.findAndModify({
query: {
name: 'comments'
},
update: {
$inc: {
counter: 1
}
},
new: true
}, function(err, doc) {
if (err) throw err;
if (!doc) {
console.dir('No counter found for comments.');
} else {
console.dir('Number of comments: ' + doc.counter);
}
});
});
Related
I'm very new to javascript/node.js and I'm having trouble with the following code. This is the handler for API an call. The 2nd code segment is just like the 1st, except there is an additional database lookup Merchant.findOne(...), and therefor the 'newTransaction.save()' function is nested one level deeper.
Both code segments return the 'output' variable value correctly. However, the second code segment does NOT also properly save the 'newTransaction' to the Mongo database.
I'm pretty sure the issue has to do with how/when the code returning from newTransaction.save(function (err, transaction){..} but I can't seem to get it straightened out.
I have been looking all over the internet trying to understand and fix this, with no success. Any help is appreciated...
Here is the older, simpler code that works as expected:
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
ticket.quote("bitstamp", "USD", 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
Here is the new code that does NOT save the newTransaction data into the Mongo DB.
handler : function(request, reply) {
var output = {
"success": true,
"operations": [],
"epoch": Date.now()
};
Terminal.findById(request.payload.deviceNumber, function (err, terminal) {
if (err) {
return reply(Boom.internal('Error looking up terminal.', err));
}
if (terminal) {
Merchant.findOne({merchantId: terminal.merchant}, function(err, merchant) {
if (err) {
console.log('Cannot find merchant');
return reply(output);
}
var processor = merchant.backendPaymentProcessor.name;
var localCurrency = merchant.localFiatCurrency;
//###################
ticket.quote(processor, localCurrency, 1, function (err, exchangeRate) {
if (err) {
console.error(err);
return reply(Boom.internal('Error obtaining ticket quote.', err));
}
var newTransaction = new Transaction({
terminal: request.payload.deviceNumber,
merchant: terminal.merchant,
ccExchangeRate: exchangeRate.buy,
fiatAmtDue: request.payload.transactionValue,
ccAmtDue: ccAmtDueTruncated
});
newTransaction.save(function (err, transaction){
if (err) {
return reply(Boom.internal('Error creating new transaction.', err));
}
output.operations.push(
{
"control": "KeyPairGenControl",
"rand": cc.pseudoRandomBytes(32).toString('hex'),
"follow": {
"url": "/pos/v1/AddressAndEncKey",
"post": {
"transactionId": transaction.transactionId
}
}
}
);
return reply(output);
});
//return reply(output);
});
//###################
});
} else {
return reply(Boom.internal('Error looking up terminal.', err));
}
});
}
I did a diff of your 2 version:
Check 1
ticket.quote
Callback are identical for both version
processor, localCurrency are different
Is exchangeRate pass into callback correct?
Check 2
newTransaction.save
newTransaction and callback for .save are setup identical
Check(console.log()) the values used in setting up new Transaction({...})
Check transaction object received by callback
Check/debug the code of Transaction.save().
I don't think the issue is with the code you posted. Both version reached return reply(output); inside newTransaction.save's callback. Very likely issue is inside Transaction class or Transaction.save() logic.
One scenario I can think of is when a transaction failed:
Transaction object is available (even for failed transaction)
Transaction Class / Transaction.save() does not write to db because transaction failed
Transaction.save() pass transaction object to callback, but NOT setting err, even when it should.
Mongoose having a feature to specify the collection name under the schema, or as the third argument when declaring the model. Otherwise it will use the pluralized version given by the name you map to the model.
Mongoose official doc having following statement:
Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.
schema-mapped:
new Schema({ <key>: <value>},
{ collection : '<collection name>' }); // collection name
model-mapped:
mongoose.model('<Model name>',
new Schema({ <key>: <value>}),
'<collection name>'); // collection name
You may also find same here
I am building backend with MEAN stack, but when I try to update document in the db i am getting an error:
topUp = function(name, amount, callback) {
User.updateOne(
{ "name" : name },
{ $set: { "wallet": amount } },
function(err, results) {
console.log(results);
callback();
});
};
TypeError: User.updateOne is not a function
But e.g. findOne() works fine:
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
i
f (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
//res.json({success: true, info: {wallet: user.wallet, userPic: user.userPic}});
topUp(decoded.name, amount, function() {
User.close();
});
}
});
"User" is a Mongo model file.
I think it's not defined in the database driver that you might be using. I think you are using Mongoose and updateOne() is not available there. You cannot use all native mongodb functions with all drivers
There is an en existing enhancement request for this https://github.com/Automattic/mongoose/issues/3997 , but maybe the findByIdAndUpdate() method could be a close alternative.
My app should update if tmx is newer, if older do nothing and if doesn't exist insert the document.
If the document is inserted, it works perfectly, else it doesn't update properly or says E11000 dup key.
trying to figure out if my callback are wrong or the logic. (I'm new to node.js+mongodb) MongoClient = require('mongodb').MongoClient,
assert = require('assert'),
url = 'mongodb://localhost:27017/pfc';
MongoClient.connect(url, function (err, db) {
run(db);
});
function run(db) {
fs.readFile('log.log', 'utf8', function (err, source) {
if (err) throw err;
var dataFile = JSON.parse(source);
dataFile.forEach(function (item) {
upsert(db, item, function (err, result) {
if (err) console.dir(err);
});
});
})
}
function upsert(db, doc, callback) {
db.collection('flags').findOne({vid: doc.vid}, function (err, item, result) {
if (item.vid != null) {
if (!(item.tmx instanceof Date)) {
item.tmx = new Date(item.tmx)
}
if(!(doc.tmx instanceof Date)){
doc.tmx = new Date(doc.tmx)
}
if (item.tmx < doc.tmx) {
console.dir("Date validation")
db.collection('flags').updateOne({vid: item.vid}, {
$set: {
"tmx": doc.tmx
}
},{upsert:true}, function (err, result) {
callback(err, result);
}
)
callback(err, result);
}
else{
console.dir("older")
callback(err, result);
}
}
else {
db.collection('flags').insertOne(doc, function(err, result) {
callback(err, result);
});
}
})}
Edit:
The documents from the 'log.log' file have this structure:
{
vid:2848
tmx: "2015-07-18T23:56:17.000Z"
}
{
vid: 2848
tmx: 2015-07-19T00:00:17.000Z
}
collection.find({vid: doc.vid},function(err,item){
if(!item) // didnt find in collection, items with vid: 2848
insert doc to collection
else if(item) //found an item with vid:2848
if (item.tmx < doc.tmx)//only update if doc.tmx is newer
update collection with the most recent document
with #Aaron Dufour help I got rid of the callback problem, thanks :)
but now the problem is when I have the collection already populated and go look for newest documents in log.log, it starts from the oldest document till the newest again :(
Your upsert is vulnerable to race conditions, and run calls it many times in parallel, so that is probably the issue. It is not clear exactly what doc will look like, so you might need slightly more complicated logic, but here's a version that uses Mongo's upsert to make things a bit safer:
function upsert(db, doc, callback) {
db.collection('flags').update({vid: doc.vid}, {$set: doc}, {upsert: true}, function(err) {
db.collection('flags').update({vid: doc.vid, tmx: {$lt: doc.tmx}}, {$set: tmx: doc.tmx}, function(err) {
callback();
});
});
}
Background:
I must create or update an document based on post request that I have zero control over. I'm calling the function updateOrCreate()
Question:
How can I properly find a document by an field called nuid without using _id in mongo/mongoose
example payload:
curl -H "Content-Type: application/json" -X POST -d '{"participant":{"nuid":"98ASDF988SDF89SDF89989SDF9898"}}' http://localhost:9000/api/things
thing.controller:
exports.updateOrCreate = function(req, res) {
//Thing.findByNuid() will not work but it will explain what i'm trying to accomplish
/**
Thing.findByNuid(req.body.participant.nuid, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) {
Thing.create(req.body.participant, function(err, thing) {
if(err) { return handleError(res, err); }
});
}
var updated = _.merge(thing, req.body.participant);
updated.save(function (err) {
if (err) { return handleError(res, err); }
});
});
**/
//this block will fetch all the things that have nuids but that seems really heavy and awful practice
Thing.find({'nuid':req.body.participant.nuid}, function(err, thing){
console.log(thing);
});
// This block is here to communicate this will create a new thing as expected.
Thing.create(req.body.participant, function(err, thing) {
if(err) { return handleError(res, err); }
});
}
Schema
var ThingSchema = new Schema({
nuid: String
});
UPDATE:
var query = {"nuid": req.body.participant.nuid};
var update = {nuid: 'heyyy'};
Thing.findOneAndUpdate(
query,
update,
{upsert: true},
function(err, thing){
console.log(thing, "thing");
console.log(err, "err");
}
);
I would use findOneAndUpdate first and then based on the result do an insert. findOneAndUpdate use mongoDB findAndModify command.
You should also look at new & upsert options of it which would create a document if not found.
Two same query gives me different records in mongo shell and in native node.js driver:
collection.find(query).limit(1).hint(hint).toArray
collection.find(query).limit(1).hint(hint).toArray
I supposed that in native node.js driver hint is ignored, and it was approved:
when I removed hint from query in node.js driver, the result was the same as I got when I used hint in node.js driver:
collection.find(query).limit(1).toArray
So maybe there is special rule to use hint in node.js mongo native driver? How to use it?
There actually isn't a .hint() method implemented in the node driver. In order to do this you supply "options" to the cursor creation in .find():
collection.find({},{ "hint": { "a": -1 }}).limit(1).toArray(function(err,docs) {
Providing that actually matches an index that is specified then you will get the documents returned in the selected index order. Perhaps a longer example:
var async = require("async"),
mongo = require("mongodb"),
MongoClient = mongo.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
async.waterfall(
[
function(callback) {
db.collection('asample',function(err,collection) {
if (err) throw err;
callback(err,collection);
});
},
function(collection,callback) {
collection.remove(function(err,removed) {
if (err) throw err;
callback(err,collection);
});
},
//
function(collection,callback) {
async.eachSeries(
[
{ "a": 1 },
{ "a": 2 }
],
function(item,callback) {
collection.insert(item,function(err,num) {
if (err) throw err;
callback();
});
},
function(err) {
if (err) throw err;
callback(err,collection);
}
);
},
function(collection,callback) {
collection.ensureIndex({ "a": -1 },function(err,index) {
if (err) throw err;
console.log("Index:\n%s", index);
callback(err,collection);
});
},
function(collection,callback) {
collection.find({},{ "hint": { "a": -1 }})
.limit(1).toArray(function(err,docs) {
if (err) throw err;
console.log("Docs:\n%s", JSON.stringify( docs, undefined, 4));
callback();
});
}
],
function(err) {
console.log("done");
db.close();
}
);
});
Where in the reverse index order the value of "2" is returned first.
Really though, "hint" should not need to be applied as long as either your "query" or .sort() specification mention the correct index to use. So that would be the better approach.