Problems with facade in javascript and Mongo - javascript

I am writting a little app to calculate and keep track of how much gas my car is using. But i have a problem with my facade.
When i am trying to add some new details to my database, i only get a empty object somehow.
I know the problem is in my facade, and maybe some of you can see what it is.
(Don't worry about the name of the method)
function addTitle (kilometer, liter, kmLiter, callback){
var data = {
kilometer: kilometer,
liter: liter,
kmLiter: kmLiter
}
detail.create({details:data}, function(err, result){
if(err)
return callback(err);
else
callback(null, result);
});
};
And this is the model of the DB
var DetailSchema = mongoose.Schema({
details:[{
kilometer: String,
liter: String,
kmLiter: String}
]
});
mongoose.model('Details', DetailSchema, "details");
Anybody that can find the error?

Assuming your schema definition of details being an array is what you want, you need to also make details an array when you create new docs.
So your function should change to:
function addTitle (kilometer, liter, kmLiter, callback){
var data = {
kilometer: kilometer,
liter: liter,
kmLiter: kmLiter
}
detail.create({details: [data]}, callback);
}

Related

How to implement query parameters in Postman with mongoose

I have a driver.js that contains a driver schema. Also the driverController.js, which contains my rest methods. GET, POST, DELETE, and PUT.
What i would like to do is
GET - http://localhost:3000/drivers?available=true
and have it return all of the drivers that are available.
My driver schema simply looks like this:
var mongoose = require('mongoose');
var DriverSchema = new mongoose.Schema({
name: String,
available: Boolean,
latitude: Number,
longitude: Number
});
mongoose.model('Driver', DriverSchema);
module.exports = mongoose.model('Driver');
I looked at some documentation, but I haven't been able to do anything.
Here's my GET method in which I'm attempting to add parameters
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({}, function (err, driver) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
var available = req.query.available;
if (available == driver.available )
res.status(200).send(available );
else
res.status(200).send("Nice! " + driver.available);
});
});
This comparison doesn't ever work. It always goes to the else statement. I'm not quite sure why but the output is "Nice! undefined" Even though I have plenty of drivers in my database, and if I only put inside the else statement
res.status(200).send("Nice! " + driver);
Then it gives me the list of drivers.
Nonetheless, I would like to be able to use query parameters in order to find drivers.
Any hints or tips would be greatly appreciated, as this is a project and I have never worked with restAPI, or javascript before. Thanks!
NOTE: Mongoose, express. node.js, and mongoDB are being used.
if I only put inside the else statement res.status(200).send("Nice! " + driver); Then it gives me the list of drivers.
it's a list of drivers, with if (available == driver.available ) you're comparing a boolean with an array of objects,
instead of fetching all the drivers and checking if they have availabe == true , add the condition to the .find() and return the result :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({ available : req.query.available }, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
EDIT :
you can do this to add filter depending on the query string :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
var params = {};
Object.keys(req.query).forEach((v, k) => params[k] = v);
Driver.find(params, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
having ?name=Wario&available=true will create an object like { name : 'wario', available : true and pass it to the .find()

MongoDB - Mongoose $inc an object inside of an array of a document

So I am trying to update the value of an object inside an array inside a document. The Schema for the document looks like this:
var pollSchema = new Schema({
title: String,
hash: String,
answers: [{answer: String, votes: Number}]
});
And this is how my current update code looks like:
module.exports.updatePoll = function(socket, data){
var answerss = 'answers.0.votes';
hash = data.url.match(/\/([^/]*)$/)[1];
Poll.findOne({'hash' : hash}, function(err, poll){
if (err) return handleError(err);
console.log(poll.answers[data.id])
});
Poll.update({hash: hash}, {$inc: {'answers.{data.id}.votes': 1}}, function(err, poll){
if (err) return console.log(err);
//socket.emit('updatePoll', hash);
});
Ive tried pasting answerss in it instead of 'answers.{data.id}.votes'and also tried some other things. It only really works when I directly paste in 'answers.0.votes'. And this is a problem because the data.id can be a value of 0 to 10. I have no idea how I would implement this and the other answers on stackoverflow or google did not give much insight on the problem.
I hope you can help me. Thanks!
You don't need the initial find from what I can tell. You can do it all in one update statement. You need to match the array element you want to update simply by specifying its index:
var answerKey = 'answers.' + data.id + '.votes';
var updateJSON = {$inc: {}};
updateJSON.$inc[answerKey] = 1;
hash = data.url.match(/\/([^/]*)$/)[1];
Poll.update({hash: hash}, updateJSON, function(err, poll){
if (err) return console.log(err);
//socket.emit('updatePoll', hash);
});

Increment a number field with Mongoose? For sorting results on .find

I have a mongoose model that looks like this:
module.exports = mongoose.model('Item', {
text : String,
position: Number
});
And I'm looking to have a Position field that increments on something like the .length of all the documents, for sorting the results of a .find of All:
// get All Items
app.get('/itemsList', function(req, res) {
// use mongoose to get all items in the database
Item.find({
sort : { position: 1 } // sort by Ascending Position
}. function(err, items) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.json(items); // return all items in JSON format
});
});
Is there a way to auto-fill a number for the Position field with some javascript in node.js?
// create an item
app.post('/api/item', function(req, res) {
// create an item, information comes from AJAX request from Angular
Item.create({
text : req.body.text,
position:
// something using ++items.length
}, function(err, item) {
if (err)
res.send(err);
});
});
Mongoose lets you hook into the save, validate and remove methods and execute code before and after they're executed.
This code can be asynchronous. For example, in your case you could probably do this:
var schema = mongoose.Schema({
text : String,
position: Number
});
schema.pre("validate", function(next) {
var doc = this;
// If 'position' is not filled in, fill it in.
// Not using !position because 0 might be a valid value.
if(typeof position !== "number") {
// Count the number of Items *
mongoose.model("Item").count(function(err, num) {
// If there was an error, pass it to next().
if(err)
return next(err);
// Update the position, then call next();
doc.position = num;
return next();
});
} else {
// There is no need to count, so call next().
next();
}
});
module.exports = mongoose.model('Item', schema);
More here.
Before validation starts, the number of Items is counted. Afterwards, the position is set.
Validation and other pre-validator ** hooks will not commence until the above code is ready.
* I'm using mongoose.model here to fetch the model because the model is not compiled yet (that happens a bit below).
** The documentation shows you how you can make multiple pre-validator hooks execute in parallel. I've chosen not to do this in this example because the code is easier to read and because you might actually need the validators to run sequentially.
In the pre-validation hook, you could place some logic in the else-case. When inserting an Item with an existing position value, you'll want to move every record down. You can do this by doing the following:
Use this.isModified("position") to check if the value was changed since you last saved. You might also need doc.isNew().
Check if there is an existing document with the same position. Something like Item.where({_id: {$ne: this._id}, position: this.position}).count()
If there is, execute: Item.update({position: {$gte: this.position}}, {position: {$inc: 1}}, {multi: 1})
Then call next() to save your doc.
The above should work. It will leave gaps when you remove documents however.
Also, look into indexes. You'll want to add one on the position field. Perhaps even a unique index.
Per #RikkusRukkus's steps for moving records down, here's logic for the else-case (to be tested)
// load mongoose since we need it to define a schema and model
var mongoose = require('mongoose');
var ItemSchema = mongoose.Schema({
text : String,
position: Number
});
// before validation starts, the number of Items is counted..afterwards, the position is set
ItemSchema.pre("validate", function(next) {
var doc = this;
// if 'position' is not filled in, fill it in..not using !position because 0 might be a valid value
if(typeof position !== "number") {
// count the number of Items *
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").count(function(err, num) {
// if there was an error, pass it to next()
if(err)
return next(err);
// set the position, then call next();
doc.position = num;
return next();
});
} else if(this.isModified("position") || this.isNew()) {
// check if there is an existing document with the same position
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").where({_id: {$ne: this._id}, position: this.position}).count( function (err, count) {
// if there was an error, pass it to next()
if(err)
return next(err);
// if there is a doc with the same position, execute an update to move down all the $gte docs
if(count > 0) {
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").update({position: {$gte: this.position}}, {position: {$inc: 1}}, {multi: 1}, function(err, numAffected) {
// Call next() (with or without an error)
next(err);
});
} else {
// there are no docs that need to move down, so call next()
next();
}
});
} else {
// there is no need to count or update positions, so call next()
next();
}
});
module.exports = mongoose.model('Item', ItemSchema);

mongoose findOne() call not saving and no error in output

I'm doing a simple findOne() and saving the doc within it, but for some reason it's not working. I've outputted the object and the output in console is correct, but after the save(), I take a look at my mongodb and it didn't save. I'm not sure if there is some sort of option I'm supposed to set. Here is my code:
var1 = "data1";
var2 = "data1field1";
Model.findOne({'_id':some_id}).exec(function(err, doc) {
if (err) return console.error(err);
(doc.data[var1][var2][0] += 1;
console.log(doc.data.data1);
doc.save(function (err) {
if(err){console.log(err);}
console.log('success');
});
});
Here is my schema:
var modelSchema = new mongoose.Schema({
'data':{
'data1':{
'data1field1':[{type: Number}],
'data1field2':[{type: Number}]
},
'data2':{
'data2field1':[{type: Number}],
'data2field2':[{type: Number}]
}
}
});
var Model = mongoose.model('model', modelSchema);
module.exports.Model = Model;
Say I create an instance of this schema where data.data1.data1field1 is an array of two numbers [0,0], the output for "console.log(doc.data.data1);" would be:
{
data1field1:[1,0],
data1field2:[0,0] }
success
But the save does not happen. I'm new to mongoose/mongodb so there is probably a simple fundamental thing I'm missing.
NOTE: I cannot use Model.update({},{$inc {}}) because I'm using variables to select which data object to change, and because of the literals in .update(), it is impossible.
Thanks.
So mongoose has some smarts to automatically detect changes when you do simple property sets like myModel.name = 'Steve'. However, when you access deeply nested schemas and change properties deep in the graph, the top level document cannot auto-detect this. So you need to tell mongoose what you changed.
doc.markModified('data.' + var1 + '.' + var2);
If you get the correct incantation of that, mongoose will be able to save your change.

How to use "q" module for refactoring mongoose code?

I'm using mongoose to insert some data into mongodb. The code looks like:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
var article1 = docs[0], article2 = docs[1];
}
});
};
You can see there are a lot of nested callbacks there, so I'm trying to use q to refactor it.
I hope the code will look like:
Q.fcall(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.end();
But I don't know how to do it.
You'll want to use Q.nfcall, documented in the README and the Wiki. All Mongoose methods are Node-style. I'll also use .spread instead of manually destructuring .then.
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
var users = conn.collection('users');
var channels = conn.collection('channels');
var articles = conn.collection('articles');
function getInsertedArticles() {
return Q.nfcall(users.insert.bind(users), [{/*user1*/},{/*user2*/}]).spread(function (user1, user2) {
return Q.nfcall(channels.insert.bind(channels), [{userId:user1._id},{userId:user2._id}]).spread(function (channel1, channel2) {
return Q.nfcall(articles.insert.bind(articles), [{userId:user1._id,channelId:channel1._id},{}]);
});
})
}
getInsertedArticles()
.spread(function (article1, article2) {
// you only get here if all three of the above steps succeeded
})
.fail(function (error) {
// you get here if any of the above three steps failed
}
);
In practice, you will rarely want to use .spread, since you usually are inserting an array that you don't know the size of. In that case the code can look more like this (here I also illustrate Q.nbind).
To compare with the original one is not quite fair, because your original has no error handling. A corrected Node-style version of the original would be like so:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
function getInsertedArticles(cb) {
// insert users
conn.collection('users').insert([{/*user1*/},{/*user2*/}], function(err, docs) {
if (err) {
cb(err);
return;
}
var user1 = docs[0], user2 = docs[1];
// insert channels
conn.collection('channels').insert([{userId:user1._id},{userId:user2._id}], function(err, docs) {
if (err) {
cb(err);
return;
}
var channel1 = docs[0], channel2 = docs[1];
// insert articles
conn.collection('articles').insert([{userId:user1._id,channelId:channel1._id},{}], function(err, docs) {
if (err) {
cb(err);
return;
}
var article1 = docs[0], article2 = docs[1];
cb(null, [article1, article2]);
}
});
};
}
getInsertedArticles(function (err, articles) {
if (err) {
// you get here if any of the three steps failed.
// `articles` is `undefined`.
} else {
// you get here if all three succeeded.
// `err` is null.
}
});
With alternative deferred promise implementation, you may do it as following:
var mongoose = require('mongoose');
mongoose.connect('mongo://localhost/test');
var conn = mongoose.connection;
// Setup 'pinsert', promise version of 'insert' method
var promisify = require('deferred').promisify
mongoose.Collection.prototype.pinsert = promisify(mongoose.Collection.prototype.insert);
var user1, user2;
// insert users
conn.collection('users').pinsert([{/*user1*/},{/*user2*/}])
// insert channels
.then(function (users) {
user1 = users[0]; user2 = users[1];
return conn.collection('channels').pinsert([{userId:user1._id},{userId:user2._id}]);
})
// insert articles
.match(function (channel1, channel2) {
return conn.collection('articles').pinsert([{userId:user1._id,channelId:channel1._id},{}]);
})
.done(function (articles) {
// Do something with articles
}, function (err) {
// Handle any error that might have occurred on the way
});
Considering Model.save instead of Collection.insert (quite the same in our case).
You don't need to use Q, you can wrap yourself the save method and return directly a Mongoose Promise.
First create an utility method to wrap the save function, that's not very clean but something like:
//Utility function (put it in a better place)
var saveInPromise = function (model) {
var promise = new mongoose.Promise();
model.save(function (err, result) {
promise.resolve(err, result);
});
return promise;
}
Then you can use it instead of save to chain your promises
var User = mongoose.model('User');
var Channel = mongoose.model('Channel');
var Article = mongoose.model('Article');
//Step 1
var user = new User({data: 'value'});
saveInPromise(user).then(function () {
//Step 2
var channel = new Channel({user: user.id})
return saveInPromise(channel);
}).then(function (channel) {
//Step 3
var article = new Article({channel: channel.id})
return saveInPromise(article);
}, function (err) {
//A single place to handle your errors
});
I guess that's the kind of simplicity we are looking for.. right? Of course the utility function can be implemented with better integration with Mongoose.
Let me know what you think about that.
By the way there is an issue about that exact problem in the Mongoose Github:
Add 'promise' return value to model save operation
I hope it's gonna be solved soon. I think it takes some times because they are thinking of switching from mpromise to Q: See here and then here.
Two years later, this question just popped up in my RSS client ...
Things have moved on somewhat since May 2012 and we might choose to solve this one in a different way now. More specifically, the Javascript community has become "reduce-aware" since the decision to include Array.prototype.reduce (and other Array methods) in ECMAScript5. Array.prototype.reduce was always (and still is) available as a polyfill but was little appreciated by many of us at that time. Those who were running ahead of the curve may demur on this point, of course.
The problem posed in the question appears to be formulaic, with rules as follows :
The objects in the array passed as the first param to conn.collection(table).insert() build as follows (where N corresponds to the object's index in an array):
[ {}, ... ]
[ {userId:userN._id}, ... ]
[ {userId:userN._id, channelId:channelN._id}, ... ]
table names (in order) are : users, channels, articles.
the corresopnding object properties are : user, channel, article (ie the table names without the pluralizing 's').
A general pattern from this article by Taoofcode) for making asynchronous call in series is :
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}
With quite light adaptation, this pattern can be made to orchestrate the required sequencing :
function cascadeInsert(tables, n) {
/*
/* tables: array of unpluralisd table names
/* n: number of users to insert.
/* returns promise of completion|error
*/
var ids = []; // this outer array is available to the inner functions (to be read and written to).
for(var i=0; i<n; i++) { ids.push({}); } //initialize the ids array with n plain objects.
return tables.reduce(function (promise, t) {
return promise.then(function (docs) {
for(var i=0; i<ids.length; i++) {
if(!docs[i]) throw (new Error(t + ": returned documents list does not match the request"));//or simply `continue;` to be error tolerant (if acceptable server-side).
ids[i][t+'Id'] = docs[i]._id; //progressively add properties to the `ids` objects
}
return insert(ids, t + 's');
});
}, Q());
}
Lastly, here's the promise-returning worker function, insert() :
function insert(ids, t) {
/*
/* ids: array of plain objects with properties as defined by the rules
/* t: table name.
/* returns promise of docs
*/
var dfrd = Q.defer();
conn.collection(t).insert(ids, function(err, docs) {
(err) ? dfrd.reject(err) : dfrd.resolve(docs);
});
return dfrd.promise;
}
Thus, you can specify as parameters passed to cascadeInsert, the actual table/property names and the number of users to insert.
cascadeInsert( ['user', 'channel', 'article'], 2 ).then(function () {
// you get here if everything was successful
}).catch(function (err) {
// you get here if anything failed
});
This works nicely because the tables in the question all have regular plurals (user => users, channel => channels). If any of them was irregular (eg stimulus => stimuli, child => children), then we would need to rethink - (and probably implement a lookup hash). In any case, the adaptation would be fairly trivial.
Today we have mongoose-q as well. A plugin to mongoose that gives you stuff like execQ and saveQ which return Q promises.

Categories