mongo insert in nodejs for loop only inserting one from array - node.js

im building an application to collect votes for a live event.
the api doesnt give us option to select users from a time frame so im polling the endpoint every second.
i currently have 13 entries that return from the endpoint, i parse them into and array and for loop around them setting my mongoose schema with the attributes and trying to save them, but when i do
db.votes.count() my result is always 1
my node module looks like
var express = require('express');
var unirest = require('unirest');
var voteSchema = require(GLOBAL.rootdir + '/modules/voting/models/votes');
var seconds = 0;
var interval = 1000;
express({
votePoller : setInterval(function () {
seconds++;
if (typeof GLOBAL.accessToken != 'undefined') {
var Request = unirest.get('https://api.domain.io/api/v1/guests');
Request
.header('Accept', 'application/json')
.header('Content-Type', 'application/json; charset=utf-8')
.header('Authorization', 'Bearer ' + GLOBAL.accessToken)
.end(function (response) {
if(response.code === 200){
var votesModel = new voteSchema;
var payloadArray = JSON.parse(response.raw_body);
for(var i in payloadArray.guests){
console.log(i);
console.log(payloadArray.guests[i]);
votesModel.ctid = payloadArray.guests[i].id;
votesModel.email = payloadArray.guests[i].username;
votesModel.voteStatus = 0;
votesModel.createdAt = new Date(1000 * payloadArray.guests[i].created_at);
votesModel.save(function(err) {
if (err) {
console.log(err);
console.log({ message: err });
} else {
console.log({ message: 'vote saved' });
}
});
console.log('Done');
}
}
});
}
console.log(seconds);
}, interval)
});
var votePoller = express;
module.exports = votePoller;
my mongoose model is
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var votesSchema = new Schema({
ctid: { type: String, required: true, unique: true },
fullName: { type: String},
email: { type: String, required: true, unique: true },
mobileNumber: { type: String },
vote: { type: Number},
voteStatus: Boolean,
createdAt: Date
});
var Votes = mongoose.model('Votes', votesSchema);
module.exports = Votes;
the console log counts out each i in the array so why the save function isn't being fired is stumping me
Thanks in advance

You need to use an async function to do an async for loop, there are many answer on here for that code. I would suggest a control flow library like async or if using a new version of node, use native promises instead. Promises all method is the best way to achieve this.

Related

Mongoose, geospatial query for users

I'm currently working with nodeJS, using express and mongoDB and mongoose for an ORM. When I create a User and save them to the database I would like to query their location and save it. This is what I am currently doing, I have a UserSchema and a location Schema.
My userSchema just has the location stored as a string and in the location Schema itself I have
var locationSchema = new mongoose.Schema({
name: String,
loc: {
type: [Number],
index: '2d'
}
});
mongoose.model('Location', LocationSchema);
And then in my controller, I have the following
import json from '../helpers/json;
import mongoose from 'mongoose';
var User = mongoose.model('User);
module.exports = function() {
var obj = {};
obj.create = function (req, res) {
var user = new User(req.body);
user.roles = ['authenticated']
user.location = getLocation(req);
user.save(function (err) {
if (err) {
return json.bad(err, res);
}
json.good({
record: user,
});
});
};
return obj;
function getLocation (req) {
var limit = req.query.limit || 10;
var maxDistance = req.query.distance || 1;
maxDistance /= 6371;
var coords = [];
coords[0] = req.query.longitude;
coords[1] = req.query.lattitude;
Location.findOne({
loc: {
$near: coords,
$maxDistance: maxDistance
}
}).limit(limit).exec(function (err, location) {
if (err) {
console.log(err);
}
return location.name;
});
}
};
I have also tried using location.find instead of findOne and returning locations[0].name.
The error is thrown says cast to the number failed for value undefined at loc.
Do I need to send the location data to the server from the client side? If so, is there a best method to implement this? I have heard of the HTML5 Geolocation API, but I have never utilized it.
Thank you!
!!! -- UPDATE --- !!
I have started using the Geolocation API on the client side to send this data to the server in the request. I am using angular on the client side like so
(function() {
'use strict';
angular.module('opinionated.authentication')
.controller('SignupController', SignupController);
/* #ngInject */
function SignupController ($state, appUsers, appToast) {
var vm = this;
vm.reset = reset;
vm.create = create;
vm.user = {
name: '',
username: '',
email: '',
password: ''
};
vm.location = {
lattitude: '',
longitude: ''
};
function create = (isValid) {
if (isValid) {
var user = new appUsers.single({
name: vm.user.name,
username: vm.user.username,
email: vm.user.email,
password: vm.user.password,
lattitude: vm.location.lattitude,
longitutde: vm.location.longitude
});
user.$save(function (response) {
if (response.success) {
appToast('Welcome to Opinionated, ' + response.res.record.name);
$state.go('authentication.wizard')
} else {
appToast(response.res.messsage);
}
});
} else {
appToast('Hmm... Something seems to be missing');
}
}
function getPosition() {
navigator.geolocation.getPosition(updatePosition);
}
function updatePosition (position) {
vm.location.lattitude = position.coords.lattitude;
vm.location.longitutde = position.coords.longitude;
}
getPosition();
....
I think it has something to do with how I am getting the coordinates now. My browser prompts me for permission to use my location, so I am at least requesting the data. However, I changed my User Schema to save the lat and long and neither of these values are being saved upon success.
I found my error. I did need to include the Geolocation API to get the users coordinates. I then just saved the coordinates to the database and am using mongo's geo service from there! Everything works fine now.

Mongoose saving for populate

I'm new to Mongoose and Nodejs developement in general and I've got a bit of confusion around how to properly set up saving my records. Here are my two schemas:
Download
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var downloadSchema = Schema({
title : String,
description : String,
_project : { type: Schema.Types.ObjectId, ref: 'Project' }
});
module.exports = mongoose.model('Download', downloadSchema);
Project
...
var projectSchema = Schema({
name : String,
url : String,
pwd : String,
_downloads : [{type: Schema.Types.ObjectId, ref: 'Download' }]
});
module.exports = mongoose.model('Project', projectSchema);
This appears to be working correctly. The documentation explains my use-case of saving a download and linking a project, but I'm not sure how to properly populate the Project._downloads. Here's what I've done:
Express route handler:
function createDownload(req, res) {
// the Project Id is passed in the req.body as ._project
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
var dload = new Download(dldata);
dload.save( function (err, download) {
project._downloads.push(download._id);
project.save( function(err){
var msg = {};
if(err) {
msg.status = 'error';
msg.text = err;
}else {
msg.status = 'success';
msg.text = 'Download created successfully!';
}
res.json(msg);
});
});
});
}
This seems overcomplicated to me. Am I supposed to be manually pushing to the ._downloads array, or is that something Mongoose is supposed to handle internally based on the schema? Is there a better way to achieve it so that I can do:
Download.find().populate('_project').exec( ...
as well as:
Project.findOne({_id : _projectId}).populate('_downloads').exec( ...
According to the mongoose docs there are 2 ways to add subdocs to the parent object:
1) by using the push() method
2) by using the create() method
So I think that your code can be a bit simplified by eliminating the operation of saving a new Download item:
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.push(dldata);
project.save(function(err) {
// handle the result
});
});
}
or
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.create(dldata);
project.save(function(err) {
// handle the result
});
});
}

mongoose writing data into array object

I have a schema that defines location as an array, into which I would like to write 2 strings (gmaps geocoding lat, long). So far I can't get it to work and can't figure out why. Any help is appreciated.
My schema:
var mongoose = require('mongoose');
var uniqueValidator = require('mongoose-unique-validator');
var Schema = mongoose.Schema;
//shop schema
var ShopSchema = new Schema({
name: { type: String, required: true, unique: true },
address: { type: String, required: true },
location: [{
latitude: String,
longitude: String
}]
});
ShopSchema.plugin(uniqueValidator);
module.exports = mongoose.model('Shop', ShopSchema);
post request:
.post(function(req, res) {
//create a shop
var shop = new Shop();
//set the shop information
shop.name = req.body.name;
shop.address = req.body.address;
//get lat and long before saving from gmaps API
//build gmaps API URL
var urlAddress = req.body.address.replace(/ /gi, '+');
var urlAPIKey = '&key=AIzaSyChkPdCaAaVZwYof8ZbKspokuYt41NlJ_0';
var url = 'https://maps.googleapis.com/maps/api/geocode/json?address=';
url = url.concat(urlAddress).concat(urlAPIKey);
//make a request
request({
uri: url,
method:"GET",
timeout: 1000
}, function(error, response, body) {
var gmaps = JSON.parse(body);
//display the geometry array
shop.location.latitude = gmaps.results[0].geometry.location.lat;
shop.location.longitude = gmaps.results[0].geometry.location.lng;
//save shop and check for errors
shop.save(function(err) {
if(err) {
return res.send(err);
}
else {
res.json({ message:'Shop created! '});
}
});
});
}) //closes .post on /shops
Basically I build an URL, make a request that returns JSON data, parse it, find it, and then try writing it. When I tried writing it without using an object (as properties on shop) it worked.
Thanks for the help
In your Shop schema, the location field is of type Array. You have to push the location object into the array after getting the response but you are trying to create an object instead of push object into array.
Change these two lines of your code from
shop.location.latitude = gmaps.results[0].geometry.location.lat;
shop.location.longitude = gmaps.results[0].geometry.location.lng;
to
shop.location.push({ latitude: gmaps.results[0].geometry.location.lat.toString(), longitude: gmaps.results[0].geometry.location.lng.toString() });

Mongoose schema inheritance and model populate

I have been trying this with the built in inheritance features of mongoose (rather than the extend plugin) but haven't been having much luck so far. This is a simplified example of code I am trying to use which exhibits the same problem. This is based on an expanded version of the mongoose documentation for schema inheritance using discriminators - http://mongoosejs.com/docs/api.html#model_Model.discriminator
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
var BossStatusSchema = new Schema({
status: String
});
var BossStatus = mongoose.model('BossStatus', BossStatusSchema);
var PersonSchema = new BaseSchema();
var Person = mongoose.model('Person', PersonSchema);
var BossSchema = new BaseSchema({
department: String,
bossStatus: {
type: ObjectId,
ref: 'BossStatus'
}
});
var Boss = Person.discriminator('Boss', BossSchema);
Example code to add the documents:
var superBoss = new BossStatus({
status: 'super'
});
var normalBoss = new BossStatus({
status: 'normal'
});
var andy = new Person({
name: 'Andy'
});
var billy = new Boss({
name: 'Billy',
bossStatus: superBoss._id
});
var callback = function(err, result) {
console.dir(err);
console.dir(result);
};
superBoss.save(callback);
normalBoss.save(callback);
andy.save(callback);
billy.save(callback);
So when finding a record without populate:
Person
.findOne({
name: 'Billy'
})
.exec(callback);
The result is as expected, the bossStatus refers to an _id from the bossstatuses collection:
null
{ name: 'Billy',
bossStatus: 52a20ab0185a7f4530000001,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
When adding the populate call:
Person
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
The resulting bossStatus property of the Person result is null:
null
{ name: 'Billy',
bossStatus: null,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
EDIT:
Ok I've just put together what is probably a better example of what I'm trying to achieve, the schema structure lends itself more to a relational DB but hopefully makes the problem clearer.
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: {
type: String,
unique: true,
required: true
}
});
}
util.inherits(BaseSchema, Schema);
var DeviceSchema = new BaseSchema();
var LocalDeviceSchema = new BaseSchema({
driver: {
type: ObjectId,
ref: 'Driver'
}
});
var RemoteDeviceSchema = new BaseSchema({
networkAddress: {
type: ObjectId,
ref: 'NetworkAddress'
}
});
var DriverSchema = new Schema({
name: {
type: String,
unique: true,
required: true
}
});
var NetworkHostSchema = new Schema({
host: {
type: String,
unique: true,
required: true
}
});
var NetworkAddressSchema = new Schema({
networkHost: {
type: ObjectId,
ref: 'NetworkHost'
},
port: {
type: Number,
min: 1,
max: 65535
}
});
var Driver = mongoose.model('Driver', DriverSchema);
var NetworkHost = mongoose.model('NetworkHost', NetworkHostSchema);
var NetworkAddress = mongoose.model('NetworkAddress', NetworkAddressSchema);
var Device = mongoose.model('Device', DeviceSchema);
var LocalDevice = Device.discriminator('LocalDevice', LocalDeviceSchema);
var RemoteDevice = Device.discriminator('RemoteDevice', RemoteDeviceSchema);
var networkHost = new NetworkHost({
host: '192.168.2.1'
});
var networkAddress = new NetworkAddress({
networkHost: networkHost._id,
port: 3000
});
var remoteDevice = new RemoteDevice({
name: 'myRemoteDevice',
networkAddress: networkAddress._id
});
var driver = new Driver({
name: 'ftdi'
});
var localDevice = new LocalDevice({
name: 'myLocalDevice',
driver: driver._id
});
var callback = function(err, result) {
if(err) {
console.log(err);
}
console.dir(result);
};
/*
// Uncomment to save documents
networkHost.save(function() {
networkAddress.save(function() {
remoteDevice.save(callback);
});
});
driver.save(function() {
localDevice.save(callback);
});
*/
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) {
case 'LocalDevice':
console.log('Would create a local device instance passing populated result');
break;
case 'RemoteDevice':
console.log('Would create a remote device instance passing populated result');
break;
}
};
Device
.findOne({name: 'myLocalDevice'})
.populate('driver')
.exec(deviceCallback);
The LocalDevice and RemoteDevice schemas could (and probably would) include other differences..
The switch would for example use a DeviceFactory or something to create the instances. My thinking was it should be possible to search the devices table for a device by 'name' and populate the collection references (if this is the correct terminology?) without having to specify the collection to search in - this was my understanding of the point of schema inheritance - or have I completely misunderstood?
Thanks for replies so far!
You are looking for a Boss, not a Person:
Boss
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
Looks like a bug. With debugging active, this is what's being shown for the population query:
Mongoose: people.findOne({ name: 'Billy' }) { fields: undefined }
Mongoose: people.find({ _id: { '$in': [ ObjectId("52a221ee639cc03d71000001") ] } }) { fields: undefined }
(the ObjectId shown is the one stored in bossStatus)
So Mongoose is querying the wrong collection (people instead of bossstatuses).
As #regretoverflow pointed out, if you're looking for a boss, use the Boss model and not the Person model.
If you do want to populate bossStatus through the Person model, you can explicitly state a model that needs to be searched for population:
.populate({
path : 'bossStatus',
model : 'BossStatus'
})
// or shorter but less clear:
// .populate('bossStatus', {}, 'BossStatus')
EDIT: (with your Device examples)
driver is part of LocalDeviceSchema, but you're querying the Device model, which has no notion of what driver is and populating driver within the context of a Device instance doesn't make sense to Mongoose.
Another possibility for populating each instance is to do it after you retrieved the document. You already have the deviceCallback function, and this will probably work:
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) { // or `device.constructor.modelName`
case 'LocalDevice':
device.populate('driver', ...);
break;
case 'RemoteDevice':
device.populate('networkAddress', ...);
break;
}
};
The reason is that the document is already cast into the correct model there, something that apparently doesn't happen when you chain populate with the find.

How to Make Mongoose's .populate() work with an embedded schema/subdocument?

I read up that you can make Mongoose auto pouplate ObjectId fields. However I am having trouble structuring a query to populate fields in a subdoc.
My models:
var QuestionSchema = new Schema({
question_text: String,
type: String,
comment_field: Boolean,
core_question: Boolean,
identifier: String
});
var SurveyQuestionSchema = new Schema({
question_number: Number,
question: {type: Schema.Types.ObjectId, ref: 'Question', required: true} //want this popuplated
});
var SurveySchema = new Schema({
start_date: Date,
end_date: Date,
title: String,
survey_questions: [SurveyQuestionSchema]
});
Right now I achieve the effect by doing:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var len = data.survey_questions.length;
var counter = 0;
var data = data.toJSON();
_.each(data.survey_questions, function(sq){
Question.findById(sq.question, function(err, q){
sq.question = q;
if(++counter == len) {
res.send(data);
}
});
});
});
Which obviously is a very error-prone way of doing it...
As I noted in the comments above, this is an issue currently under scrutiny by the mongoose team (not yet implemented).
Also, looking at your problem from an outsider's perpsective, my first thought would be to change the schema to eliminate SurveyQuestion, as it has a very relational db "join" model feel. Mongoose embedded collections have a static sort order, eliminating the need for keeping a positional field, and if you could handle question options on the Survey itself, it would reduce the schema complexity so you wouldn't need to do the double-populate.
That being said, you could probably reduce the queries down to 2, by querying for all the questions at once, something like:
Survey.findById(req.params.id, function(err, data){
if(err || !data) { return handleError(err, res, data); }
var data = data.toJSON();
var ids = _.pluck(data.survey_questions, 'question');
Question.find({_id: { $in: ids } }, function(err, questions) {
_.each(data.survey_questions, function(sq) {
sq.question = _.find(questions, function(q) {
var id = q._id.toString();
return id == sq.question;
});
});
res.send(data);
});
});

Resources