Mongoose & float values - node.js

My lat & lng numbers are being converted to strings. My section integers are still the correct data type of Number. How do I set up model so that I can get my lat & lng back out as Float rather than String?
I'm storing latLng data in my db. Right now I have my data type set to Number for lat & lng. When I check out my db I see this:
{
"_id" : ObjectId("563bd98a105249f325bb8a7e"),
"lat" : 41.8126189999999980,
"lng" : -87.8187850000000054,
"created" : ISODate("2015-11-05T22:34:50.511Z"),
"__v" : 0,
"section" : 0,
}
But when I get my data back out using express I get this:
{
"_id": "563bd98a105249f325bb8a7e",
"lat" : "41.8126189999999980",
"lng" : "-87.8187850000000054",
"__v": 0,
"section" : 0,
"created" : "2015-11-05T22:34:50.511Z",
}
My model:
var WaypointSchema = new Schema({
lat: {
type: Number
},
lng: {
type: Number
},
section: {
type: Number
}
created: {
type: Date,
default: Date.now
}
});
mongoose.model('Waypoint', WaypointSchema);
Express controller:
exports.list = function(req, res) {
Waypoint.find().sort('-created').populate('user', 'displayName').exec(function(err, waypoints) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(waypoints);
}
});
};

While the mongoDB fully supports float type, the mongoose supports only type of Number which is integer. If you try to save to mongoDB float number using mongooses type of Number it will be converted to string.
To sort this out, you will need to load some plugin for mongoose which will extend its value types. There are some plugins which work best with currencies or dates, but in your case I would use https://www.npmjs.com/package/mongoose-double.
Your model after changes would look something like this:
var mongoose = require('mongoose')
require('mongoose-double')(mongoose);
var SchemaTypes = mongoose.Schema.Types;
var WaypointSchema = new Schema({
lat: {
type: SchemaTypes.Double
},
lng: {
type: SchemaTypes.Double
},
section: {
type: Number
}
created: {
type: Date,
default: Date.now
}
});
mongoose.model('Waypoint', WaypointSchema);
Hope it helps.

As of the current version of mongoose (v5.12.6), It supports Decimal128 which can be used for this.

var mongoose = require('mongoose');<br>
var Schema = mongoose.Schema;<br>
var Waypoint = new Schema({<br>
lat: {<br>
type: SchemaTypes.Double<br>
},<br>
lng: {<br>
type: SchemaTypes.Double<br>
},<br>
section: {<br>
type: Number<br>
}<br>
point: {<br>
type: [Number],<br>
index: '2d'<br>
},<br>
}, {<br>
timestamps: true<br>
})<br>
event.index({<br>
Point: '2dsphere'<br>
});<br>
module.exports = mongoose.model('Waypoint', Waypoint);<br>
waypoint.save(point: [parseFloat(values.latitude), parseFloat(values.longitude)],)

Related

Mongoose : update a document in a pre save hook

i'm quite new to nodeJS and have some problem i cannot solve...
i would like to update a related document in a pre save hook in mongoose :
No errors, but as a result i always get:
updated:{"n":1,"nModified":0,"ok":1}
so document is never updated.... how can i solve this ? i know i can make all controls before, but pre-hook seems the good place for that.
thanks for your help...
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var suivilotSchema = new Schema({
code_lot: { type: String,Required: 'Code Lot cannot be left blank.' },
site_IMS: { type: String,Required: 'Site IMS cannot be left blank'},
date_plantws: { type: String,Required: 'tws plan prod cannot be left blank'},
nb_server: { type: Number,Required: 'nb Server cannot be left blank.',default: 1},
nb_server_done: { type: Number,Required: 'nb Server done cannot be left blank.',default: 0},
date_created: { type: Date,default: Date.now },
date_updated: { type: Date },
retry: { type: Number },
status: { type: Number}
});
suivilotSchema.pre('save', function (next) {
console.log('***** PRE HOOK suivilot *******');
var self = this;
this.constructor.findOne({'code_lot' : self.code_lot,'date_plantws' : self.date_plantws, 'retry' :
self.retry },function (err,existinglot) {
if (!existinglot){
console.log('SUIVILOTS : pas de resultat');
next();
}
else{
console.log('SUIVILOTS : lot exists: ',existinglot._id);
existinglot.updateOne({ '_id' : existinglot._id }, { $inc:{'nb_server' : 1}
},function(err,updated){
if(err)
console.log("Error during increment server :"+err);
console.log('updated:'+JSON.stringify(updated));
console.log('updated:'+JSON.stringify(existinglot));
});
}
});
});

Query complains about missing 2dsphere-index, but it's there

When I execute the following code (a larger example, boiled down to the essentials)
var mongoose = require("mongoose");
var LocationSchema = new mongoose.Schema({
userName: String,
loc: {
'type': { type: String, enum: "Point", default: "Point" },
coordinates: { type: [Number] }
}
})
LocationSchema.index({ category: 1, loc: "2dsphere" });
var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://user1:test#ds042417.mlab.com:42417/locationdemo';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });
var testUser = Location({
userName: "Tester",
loc: { coordinates: [12.44, 55.69] }
});
testUser.save(function (err) {
if (err) {
return console.log("UPPPPs: " + err);
}
console.log("User Saved, Try to find him:");
let query = Location.find({
loc:
{
$near:
{
$geometry:
{
type: "Point",
coordinates: [12.50, 55.71]
},
$maxDistance: 600000
}
}
})
query.exec(function (err, docs) {
if (err) {
return console.log("Err: " + err);
}
console.log("Found: " + JSON.stringify(docs))
})
});
I get this error:
Err: MongoError: error processing query: ns=locationdemo.locationsTree: GEONEAR field=loc maxdist=600000 isNearSphere=0
Sort: {}
Proj: {}
planner returned error: unable to find index for $geoNear query
But the index is there (see line 10) and the screenshot from mlab below. What am I doing wrong?:
You are breaking a rule of how you can use a an index in general. Whilst it is true that there is no restriction that a "2dsphere" index be the "first" property in a compound index, it is however very important that your "queries" actually address the first property in order for the index to be selected.
This is covered in Prefixes from the manual on compound indexes. In excerpt:
{ "item": 1, "location": 1, "stock": 1 }
The index has the following index prefixes:
{ item: 1 }
{ item: 1, location: 1 }
For a compound index, MongoDB can use the index to support queries on the index prefixes. As such, MongoDB can use the index for queries on the following fields:
the item field,
the item field and the location field,
the item field and the location field and the stock field.
However, MongoDB cannot use the index to support queries that include the following fields since without the item field, none of the listed fields correspond to a prefix index:
the location field,
the stock field, or
the location and stock fields.
Because your query references "loc" first and does not include "category", the index does not get selected and MongoDB returns the error.
So in order to use the index you have defined, you need to actually query "category" as well. Amending your listing:
var mongoose = require("mongoose");
mongoose.set('debug',true);
var LocationSchema = new mongoose.Schema({
userName: String,
category: Number,
loc: {
'type': { type: String, enum: "Point", default: "Point" },
coordinates: { type: [Number] }
}
})
//LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });
LocationSchema.index({ category: 1, loc: "2dsphere" });
var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });
var testUser = Location({
userName: "Tester",
category: 1,
loc: { coordinates: [12.44, 55.69] }
});
testUser.save(function (err) {
if (err) {
return console.log("UPPPPs: " + err);
}
console.log("User Saved, Try to find him:");
let query = Location.find({
category: 1,
loc:
{
$near:
{
$geometry:
{
type: "Point",
coordinates: [12.50, 55.71]
},
$maxDistance: 600000
}
}
})
query.exec(function (err, docs) {
if (err) {
return console.log("Err: " + err);
}
console.log("Found: " + JSON.stringify(docs))
})
});
As long as we include "category" everything is fine:
User Saved, Try to find him:
Mongoose: locations.find({ loc: { '$near': { '$geometry': { type: 'Point', coordinates: [ 12.5, 55.71 ] }, '$maxDistance': 600000 } }, category: 1 }, { fields: {} })
Found: [{"_id":"59f8f87554900a4e555d4e22","userName":"Tester","category":1,"__v":0,"loc":{"coordinates":[12.44,55.69],"type":"Point"}},{"_id":"59f8fabf50fcf54fc3dd01f6","userName":"Tester","category":1,"__v":0,"loc":{"coordinates":[12.44,55.69],"type":"Point"}}]
The alternate case is to simply "prefix" the index with the location. Making sure to drop previous indexes or the collection first:
LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });
As well as you probably should be in the habit of setting "background": true, else you start running into race conditions on unit tests where the index has not finished being created before unit test code attempts to use it.
My first solution to this problem was to create the index via the mLab web-interface which worked like a charm.
I have tried the solution suggested by Neil, but that still fails. The detailed instructions related to indexes, given by Neil however, did point me toward the solution to the problem.
It was a timing problem (which you not always see if you run the database locally) related to that my test code did the following:
Created the index, created a Location document (which first time will also create the collection), and then in the callback provided by save, I tried to find the user. It seems that the index was not yet created here, which is what gave the error.
If I delay the find method a second, using setTimeout it works fine.
But still, thanks to Neil for valuable information about the right way of using indexes (background) :-)

MongoDB aggregate, geNear and iterate over callback

I have a problem and I can´t find a solution. I have some MongoSchemas where I store Geolocation from users. Mobile Phone is sending me longitude and latitude every 5 minutes. This API is working perfectly.
Mongo-Schema looks like:
// Importing Node packages required for schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//= ===============================
// User Schema
//= ===============================
const GeolocationSchema = new Schema({
loc: {
type: { type: String },
coordinates: { type: [Number], index: '2dsphere' }
},
user: { type: Schema.Types.ObjectId, ref: 'User' }
},
{
timestamps: true
});
module.exports = mongoose.model('Geolocation', GeolocationSchema);
Now, I want to calculate users-nearby which have an "updateAt"-timestamp not even longer than 5 minutes in the past. That means that one or more users can be in a distance of e.g. 500m until 5 minutes in the past. This should be a match. For this I use Mongo aggregate, and I want to iterate the callback-result and extract the user._id out of the result to build a match.
This is what I tried:
const Geolocation = require('../models/geolocation')
User = require('../models/user'),
config = require('../config/main');
exports.setGeolocation = function (req, res, next) {
// Only return one message from each conversation to display as snippet
console.log(req.user._id);
var geoUpdate = Geolocation.findOneAndUpdate( { user: req.user._id },
{ loc: {
type: 'Point',
coordinates: req.body.coordinates.split(',').map(Number)
},
user: req.user._id
},
{upsert: true, new: true, runValidators: true}, // options
function (err, doc) { // callback
if (err) {
console.log(err);
}
});
// create dates for aggregate query
var toDate = new Date( (new Date()).getTime());
var fromDate = new Date( (new Date()).getTime() - 5000 * 60 );
var match = Geolocation.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: req.body.coordinates.split(',').map(Number)
},
distanceField: "dist.calculated",
maxDistance: 500,
includeLocs: "dist.location",
uniqueDocs: true,
query: { user: {$ne: req.user._id } , updatedAt: { $gte: fromDate,$lte: toDate }},
num: 5,
spherical: true
}
}], function (err, doc){
//here I´m going in trouble correctly parsing doc
var str = JSON.stringify(doc);
var newString = str.substring(1, str.length-1);
var response = JSON.parse(newString);
console.log(response.user);
});
res.sendStatus(200);
};
As you can see I´m going in trouble in parsing the "doc"-callback to iterate over the documents. If I want to parse it as jSON I´m getting an token-error on position 1. If I have more than 2 results, I´m getting an error on position 288.
That´s why I tried to parse and stringify the "doc". But this is not working correctly.
Maybe, someone could help me with a solution. I´m not familiar with mongo-functions because I´m starting with it, maybe there is a better solution but I can´t find something else to calculate geoNear and iterate afterwards over the results.
Thx at all who can help...

mongodb date insert like using mongoose

I am inserting bulk records in mongodb. I am using the native DB drivers to do this, as the performance is much higher. At other points in my application, i am using mongoose. The problem I am having is that mongoose translates the date into a different format whereas mongodb native just inserts it as the number of seconds since 1970. So later queries in mongoose based off that date do not work.
Here's my mongoose schema:
var MySchema = new Schema({
name : { type: String, required: true },
updatedAt : Date
});
And my mongo db mass insert:
var newRec = {
name : entry.Name,
updatedAt : Date.now
};
newRecords.push(newRec);
MySchema.collection.insert(newRecords, function(err, newRecs) {
res.json(newRecs.ops);
});
This produces in the DB:
{
"_id": {
"$oid": "562818ecf24d540f0053a38d"
},
"name": "Cool Record",
"updatedAt": 12312423512
}
Whereas if it was run through Mongoose it would produce:
{
"_id": {
"$oid": "561fd90285b5e73f5626f74e"
},
"name": "Cool Record",
"updatedAt": {
"$date": "2015-10-20T20:01:17.553Z"
}
}
If going through mongoose, queries like this work well:
MySchemda.find({ updatedAt : { $gt: lastSynced }}).exec();
But do not work otherwise.
Date.now is a number representing milliseconds since 1970. While it conceptually represents a date, it isn't actually a Date:
var x = Date.now;
typeof x;
// "number"
You need to switch your schema to be:
var MySchema = new Schema({
name : { type: String, required: true },
updatedAt : Number
});
alternately, you can use:
var newRec = {
name: entry.Name,
updatedAt: new Date()
}
and keep your schema as it is.
You may use it like this:
Define the date type and the default value, then create a variable which can be defined as your schema use year : new Date this would help a lot to set the date.
var songSchema = new mongoose.Schema({
name : String,
year : {type : Date, default : Date.now},
singer : String,
});
var song = mongoose.model("song", songSchema);
var xyz= new song({
name : "abcd",
year :new Date, // to set the date first set new Date
singer : "aabbccdd",
});
You can use setDate , setMonth, setYear method to solve the issues.There are more methods defined under the object year .You can out further at the documentation of mongoose.
xyz.save(function(err, songs){
if(err)
winston.log("Something is wrong"+ " "+ err);
else {
songs.year.setDate(03);
songs.year.setMonth(03);
songs.year.setYear(2015);
winston.log(songs);
}
});

How to update mixed type field in Mongoose without overwriting the current data?

I have the following schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: Schema.Types.Mixed,
country: {
type: String,
default: ''
},
createdAt: {
type: Date,
default: Date.now
},
defaultLanguage: {
type: String
},
account: {type : Schema.ObjectId, ref : 'Account'},
});
mongoose.model('Shop', ShopSchema);
"name" field is multilingual. I mean, I will keep the multilingual data like
name: {
"en": "My Shop",
"es": "Mi Tienda"
}
My problem is, in a controller, I am using this code to update the shop:
var mongoose = require('mongoose')
var Shop = mongoose.model('Shop')
exports.update = function(req, res) {
Shop.findByIdAndUpdate(req.params.shopid, {
$set: {
name: req.body.name
}
}, function(err, shop) {
if (err) return res.json(err);
res.json(shop);
});
};
and it is obvious that new data overrides the old data. What I need is to extend the old data with the new one.
Is there any method to do that?
You should to use the method .markModified(). See the doc http://mongoosejs.com/docs/schematypes.html#mixed
Since it is a schema-less type, you can change the value to anything else you like, but Mongoose loses the ability to auto detect and save those changes. To "tell" Mongoose that the value of a Mixed type has changed, call the .markModified(path) method of the document passing the path to the Mixed type you just changed.
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved
Use "dot notation" for the specific element:
Shop.findByIdAndUpdate(req.params.shopid, {
"$set": {
"name.en": req.body.name
}
}, function(err, shop) {
if (err) return res.json(err);
res.json(shop);
});
});
That wil either only overwrite the "en" element if that is what you want to do or "create" a new element with the data you set it to. So if you used "de" and that did not exist there will be the other elements and a new "de" one with the value.

Resources