Why doesn't jasmine-node mongoose test wait, as expected? - node.js

I am writing a simple app that saves and looks up locations. I'm using mongoose and jasmine-node.
User CRUD test work as expected. However, I created the users individually to test different custom validations. I also start the tests by clearing the collection and re-loading all the users as a way to be sure all test data is good before launching into save/update/etc tests.
For locations, I am doing the same but I have several dozen locations and I wanted to load them using an array...and test the load along the way to be sure that it works fine.
If I only do one location, it works fine. More than one and they fail.
I know I'm missing some async related step here, but I'm either searching for the wrong terms or I'm too close to it now to see the fundamentally simple mistake I'm making here.
Versions:
mongoose: 3.6.16
jasmine-node: 1.11.0
mongodb: 2.4.5
Details
The test...
it("creating location succeeds", function(done){
for(var locIndex in testLocations) {
locations.create(testLocations[locIndex], function(err, location){
// console.log says location is undefined
// unless there is only one location, then it works.
expect(err ).toBeNull();
expect(location.name ).toBe(testLocations[locIndex].name);
done();
});
}
});
...and the create function from a separate file holding location related functions...
exports.create = function(location, cb){
var newLocation = new Location(location);
// console.log says we make it into here...
newLocation.save(function(err, newLocation){
// ...but we never make it in here, except when there's only one location
if (err) {
cb(err, null);
} else {
cb(null, newLocation);
}
});
};
...and some test locations...
var testLocations = [
{
"name" : "A Great Noodle Place",
"street" : "1234 Elm Street",
"city" : "Springfield",
"phone" : "(123) 456-7890",
"website" : "n00dlesrus.com",
"district" : "Downtown"
},
{
"name" : "Perfect Pizza Palace",
"street" : "1234 Professor Ave",
"city" : "Springfield"
"phone" : "(321) 654-0987",
"website" : "cheesegalore.com",
"district" : "Uptown"
}
]
Thanks!

You're calling done() inside a loop. So it gets called in the first iteration. That's why it works when there's only 1. You could try using async, which will iterate over a list and call a final callback when finished:
it("creating location succeeds", function(done){
async.each(Object.keys(testLocation), function(key, callback){
locations.create(testLocations[key], function(err, location){
expect(err).toBeNull();
expect(location.name).toBe(testLocations[key].name);
callback();
});
}, function(err) {
done();
});
});

Related

Does MongoDB have a way to update a document without dropping existing elements not contained in the update data

I have been using MongoDB with both .NET core and Node.js. This question is specific to a node project I am working on, but I think the client language is irrelevant to my questions.
I have a document that has elements like name and address current ("add_current") below:
{
"_id" : ObjectId("5d858718c1d9f856881dc9ce"),
...
"name": "John Doe",
"add_current" :
{
"add1" : "456 Old Ave.",
"city" : "Stafford",
"state" : "VA",
"zip" : "23234"
},
...
}
Sometimes I am just updating some parts of the child object like this:
const updateDocument2 = function (db, callback) {
const collection = db.collection('documents');
collection.update(
{
'_id': ObjectID("5d858718c1d9f856881dc9ce")
},
{
$set: {
name: "John Doe Jr.",
add_current: {
add1: "123 New Street",
}
}
}, function (err, result) {
console.log("Updated the document");
callback(result);
});
}
When I execute the $set above, I delete the fields city, state and zip. I don't want to do that.
I am seeking the most efficient way to update name and add_current.add1 without deleting other fields (like add_current.state). I realize that there are ways to do this with multiple touches to the data record (.findOne(...) then .update(...)). Is there a way to do it with a single .update(...) touch?
you are setting add_current's value to {add1: "123 New Street"}
try {$set:{"add_current.add1": "123 New Street"}}

Very slow update performance

I am parsing a CSV file, for each row I want to check if corresponding entry exists in the database, and if it does I want to update it, if it doesn't I want to enter a new entry.
It is very slow - only around 30 entries per second.
Am I doing something incorrectly?
Using node, mongodb, monk
function loadShopsCSV(ShopsName) {
var filename = 'test.csv'
csv
.fromPath(filename)
.on("data", function(data) {
var entry = {
PeriodEST: Date.parse(data[0]),
TextDate: textDateM,
ShopId: parseInt(data[1]),
ShopName: data[2],
State: data[3],
AreaUS: parseInt(data[4]),
AreaUSX: AreaUSArray[stateArray.indexOf(data[3])],
ProductClass: data[5],
Type: data[6],
SumNetVolume: parseInt(data[7]),
Weekday: weekdayNum,
WeightedAvgPrice: parseFloat(data[8]),
}
db.get(ShopsDBname).update(
{"PeriodEST" : entry.PeriodEST,
"ShopName": entry.ShopName,
"State" : entry.State,
"AreaUS" : entry.AreaUS,
"ProductClass" : entry.ProductClass,
"Type" : entry.Type},
{$set : entry},
function(err, result) {
}
);
}
}
})
.on("end", function() {
console.log('finished loading: '+ShopsName)
});
}, function(err) {
console.error(err);
});
}
First I would suggest to localize problem:
replace .on("data", function(data) with dummy .on("data", function() {return;}) and confirm speed of csv parsing.
turn on mongo profiler db.setProfilingLevel(1) and check slow log if there is any query slower than 100 ms.
If there are no problems above - the bottleneck is in one of nodejs libraries you are using to prepare and send query.
Assuming the problem is with slow mongodb queries, you can use explain for the update query for details. It may be the case it does not use any indexes and run a table scan for every update.
Finally, it is recommended to use bulk operations, which was designed for exactly your usecase.
Have you tried updating with no write concern? as MongoDB blocks until whole update is successful and DB sends back that acknowledgement? Are you on cluster or something? (might want to write into primary node if so)
after your {$set : entry},
{writeConcern: {w: 0}}

Trying to upsert mongodb subdocument array with node.js

I have a straightforward mongo collection with an array of subdocuments. I'm trying to do the oft asked "upsert a subdocument in an array". I have read all questions on this topic, but can't seem to get it to work.
Data structure for game_managers:
{
"_id" : ObjectId("555cf465715ff974fb09221f"),
"game_id" : "123456789",
"players" : [
{
"request_email" : "thebigcheese#foobar.com",
"request_notes" : "I love mongo!",
"user_id" : ObjectId("551eb55f555b404d68b88063")
},
{
"request_email" : "morecowbell#example.com",
"request_notes" : "I love oysters!",
"user_id" : ObjectId("551eb55f555b404d68b88063")
}
]
}
When I try to Create / Update with the following code, it always overwrites the first element. I can't get it to even
var col = db.mongo.collection('game_managers');
// Upsert a game manager record for the game
col.update( {game_id:game.place_id}, {$setOnInsert:{game_id:game.game_id}}, { upsert: true }, function(err, result, upserted) {
// Append or update game manager record.
col.update(
{game_id:game.place_id},
{$addToSet: {"players":fields}},
function(err, result) {
next();
}
);
});
I modelled the code from this similar question however it doesn't apply to arrays of subdocuments. I do not want to $pull, and then $push a new element, as the subdocument will ultimately have timestamps and some comments[{},{},{}] subdocs on them.

How do I sort 'auto-magically' using Mongoose?

Say I have a URL like this:
http://dev.myserver.com/systems?system_type.category=Penetration
which hits the following controller:
exports.findAll = function(req, res) {
System.find(req.query, function(err, systems) {
res.send(systems);
});
};
and then returns the set below 'auto-magically' using Node, Express, MongoDB and Mongoose:
[{
"_id": "529e5f29c128685d860b3bad",
"system_number": "123",
"target_country": "USA",
"system_type": {
"category": "Penetration",
"subcategory": ["Floor", "Wall"]
}
},{
"_id": "999e5f29c128685d860b3bad",
"system_number": "456",
"target_country": "Canada",
"system_type": {
"category": "Penetration",
"subcategory": ["Floor", "Wall"]
}
}]
Now, if I want the results sorted by 'target_country', what is the 'best practice' for 'auto-magically' doing that?
Are there certain parameters/syntax that Mongoose/Express are expecting to do it for me? Or, is this a case where I have to specifically code for it? (That would kill the 'auto-magical' functionality already there.)
Thanks!
UPDATE: Here is what worked for me.
exports.findAll = function(req, res) {
// Sort Ascending:
http://dev.dom.com/systems?system_type.category=Penetration&sort=system_number
// Sort Descending:
http://dev.dom.com/systems?system_type.category=Penetration&sort=-system_number
// Default sort ascending on system_number:
http://dev.dom.com/systems?system_type.category=Penetration
var sort_param = req.query.sort ? req.query.sort : 'system_number';
System.find().sort(sort_param).find(function(err, menus) {
res.send(menus);
});
};
I guess where I went wrong, was to think I should find with filters and then sort, instead of find all, sort and then find again with filters. Still getting my head wrapped around the whole 'callback philosophy' I guess.
You need to define separate URL parameters for the query and sort components of your System query. As in:
System.find(req.query.query).sort(req.query.sort).exec(function(err, systems) {
res.send(systems);
});
Then you'd use request URL parameters that look like:
?sort=target_country
&query[system_type.category]=Penetration
&query[system_type.subcategory]=Floor
Docs on sort here.

NodeJS + Mongo native – check if collection exists before query

I've got a function, trying to get a specific value from settings collection in MongoDB. The marker for settings object, containing settings values, in settings collection is {'settings':'settings'}. The schema is:
collection:setting
|--object
|--{'settings':'settings'}
|--{'valueA':'valueA'}
|--...
The problem is when I first time query settings object, the collection 'settings' simply does not exists. So,
exports.getInstruments = function (callback) {
db.collection("settings", function(error, settings) {
settings.find({ "settings" : "settings" }), (function(err, doc) {
callback(doc.instruments);
});
]);
}
just hangs and callback is not invoked. If collection does not exist, I should return "" or undefined, else - doc.instrumens.
There's an exists() function that you could use to determine whether or not to execute the code that hangs.
> db.getCollection('hello').exists()
null
> db.getCollection('world').exists()
{ "name" : "testdb.world" }
You could potentially take advantage of db.createCollection which explicitly creates a collection:
> db.createCollection("asd")
{ "ok" : 1 }
> db.createCollection("asd")
{ "errmsg" : "collection already exists", "ok" : 0 }
Just check if the command succeeded based on the ok field.
You shouldn't need to specially handle the new collection case, I think the problem is with your code.
Aside from some syntax problems, the main problem is that find passes a Cursor to your callback function, not the first matching document. If you're expecting just one doc, you should use findOne instead.
This should work:
exports.getInstruments = function (callback) {
db.collection("settings", function(error, settings) {
settings.findOne({ "settings" : "settings" }, function(err, doc) {
callback(doc && doc.instruments);
});
});
};

Resources