mongoose geojson in schema, "Can't extract geo keys" error - node.js

I have a mongodb collection with a defined schema, and I updated this schema to include lat/lon coordinates.
old version:
var schema = mongoose.Schema({
id: String,
name: String,
address: String,
city: String,
zip: String,
country: String,
phoneNumber: String,
mobile: String,
website: String,
email: String,
});
new version
var schema = mongoose.Schema({
id: String,
name: String,
address: String,
city: String,
zip: String,
country: String,
phoneNumber: String,
mobile: String,
website: String,
email: String,
location: GeoJSON.Point
});
schema.index({ location: '2dsphere' });
GEOJSON.Point comes from mongoose-geojson-schema and looks like this:
GeoJSON.Point = {
'type' : { type: String, default: "Point" },
coordinates: [
{type: "Number"}
]
}
The collection already contained data before I added the location property.
Apparently what happens now is that for some reason mongodb uses { coordinates: [], type: "Point" } as default value for the existing documents, and I get errors like these:
MongoError: Can't extract geo keys: { _id: ObjectId('5637ea3ca5b2613f37d826f6'), ...
Point must only contain numeric elements
I have looked at how to specify a default value in a schema, but I see no way of setting the value to null in case of a GeoJSON.Point data type.
I also tried
db.collection.update({},{$set:{location:null},{multi:true})
but that didn't seem to help either.
Is it because of the index on the location?

I think you need to upgrade GeoJSON.Point to a sub document with a proper schema:
GeoJSON.Point = new mongoose.Schema({
'type' : { type: String, default: "Point" },
coordinates: [ { type: "Number" } ]
});
Combined with the minimize option, which it enabled by default, this will make Mongoose only save the location property if it is actually set.

I would suggest you to not use GeoJSON.Point as a schema type. Just use mixed object type and everything will work properly.
location: Schema.Types.Mixed

Related

MongoDB/Mongoose: Sorting within a populated field

I am pretty new to MongoDB and i am unable to find a solution to the following:
I am trying to sort personnel after their ranks.
CollectionPerson: _id, name, {rank_id}
CollectionRank: _id, RankName, level
What I am trying to accomplish ist to get a list in order of the rank level.
Any solution or direction pointing would be nice.
/edit:
MyModels:
const RankSchema = new Schema({
level: Number,
dgGrp: Number,
dgSold: String,
dgNATO: String,
hut: {
bezX: String,
bezM: String,
bezS: String,
img: String
},
lut: {
bezX: String,
bezM: String,
bezS: String,
img: String
},
mut: {
bezX: String,
bezM: String,
bezS: String,
img: String
}
});
const PersonalSchema = new Schema({
name: {
type: String,
required: true,
},
vname: String,
pNum: {
type: String,
default: '01010101',
},
pKenn: {
type: String,
default: '010101-A-01010',
},
dg: {
type: Schema.Types.ObjectId,
required: true,
ref: 'Rank'
},
uni: String,
sex: String,
konf: String,
adresse: {
str: String,
plz: Number,
ort: String,
land: String,
staat: String
}
});
My Query:
const personal = await Personal.find({}).populate({ path: 'dg', options: { sort: { level: 1 } } });
Population is done on client side by Mongoose after the query fetching the base documents has already executed. As such, sorting the base documents has already been done (if requested).
You can:
Construct an aggregation pipeline, $lookup and $sort yourself.
Let Mongoose populate your documents then reorder them as you wish in your application.

MongoDB: How do you create an index on a field which is nested in two arrays?

I have a Mongo schema which essentially has two nested arrays, each of which contain objects. I would like to create an index on a field nested in that second level array. The schema looks as such:
const restaurantsSchema = new Schema({
restaurantName: { type: String, required: true },
dishes: [{
dishName: String,
price: Number,
description: String,
reviews: [{
reviewId: Number, **<<<< INDEX THIS FIELD**
userId: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true,
},
photoUrl: String,
caption: String,
createdAt: Number,
updatedAt: Number,
rating: Number,
reviewText: String,
}],
}],
});
The idea is that I can use this index to find and update a user's reviews. Is this possible with Mongo's multikey indexing or is there another way to do this? Any help would be appreciated.
I believe if you change the schema to
reviewId: {
type: Number,
unique: true
}
mongodb automatically creates an index because each value should be unique. That really the only qualification for creating any index.
write back if it works!

How to find documents in mongoose by populate field?

I have an issue with mongoose. When I make some field referenced to other collection, I lose ability to search by this field. I don't know how to describe my problem correctly, so look at the examples please.
Schema:
var PostSchema = new Schema({
title: String,
content: String,
url: String,
author: { type: Schema.ObjectId, ref: 'User'},
mainImage: String,
type: String
});
Query:
Post.find({author: user._id})
.then(posts => res.send(posts))
.catch(err => res.status(500).send(err))
Returns nothing. But if I change "author" field to String, it will works, but without populate. :(
Upd:
I can't believe. I made this:
var PostSchema = new Schema({
title: String,
content: String,
url: String,
author: {type: String, ref: 'User'},
mainImage: String,
type: String
});
Just change type to string. Omg I can't figure out how it working. How mongoose knows which field I need to compare in ref collection? I mean there is no direct link to "_id" field (see query). Can someone explain?
Upd2:
Auhtor schema:
var UserSchema = new Schema({
id: String,
about: String,
firstname: String,
lastname: String,
email: String,
avatar: String,
city: String,
country: String,
dateOfBirth: String,
password: String,
},
{
timestamps: true
})
As you can see, I using additional "id" field just in purpose to give users simple numeric id for url (/id1 etc). But I am sure this isn't the source of the problem :)
Try change the type: Schema.ObjectId to type: mongoose.Schema.Types.ObjectId

Add uppercase: true to mongoose embedded document

If I have two schemas, one which will be embedded in the other:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Will embed this in the personSchema below
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
module.exports = mongoose.model("Person", personSchema);
I can't seem to get the uppercase: true to work for embedded documents - no error is thrown, but it simply doesn't uppercase the state property. Or any kind of option like that.
I've been searching the Mongoose docs, but maybe I'm just not finding where it mentions that settings these kinds of additional options on subDocuments won't work.
Up until recently, Mongoose would throw an exception if you tried to directly embed one schema within another like you're doing. It looks like it's partially supported now, but apparently not for cases like this.
You can get this to work by using just the definition object from addressSchema instead of the schema itself in the definition of the address field of personSchema.
var addressObject = {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
};
var addressSchema = new Schema(addressObject);
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressObject
});
Not positive if this is the best way to do it or not, but I added a pre-save hook (per the suggestion of #nbro in the comments) and that seems to be working:
var addressSchema = new Schema({
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
});
addressSchema.pre("save", function (next) {
this.state = this.state.toUpperCase();
next();
});
var personSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
emailAddress: {
type: String,
lowercase: true
},
phoneNumber: Number,
address: addressSchema
});
Update #1:
I seem to be able to find lots of cases of people embedding simple schemas without any additional validation (required: true) or alteration (uppercase: true) occurring. While the above solution does work, it seems kind of unnecessary. What I should probably be doing is just putting in the object literal to embed the info:
var personSchema = new Schema({
...
address: {
street: String,
city: String,
state: {
type: String,
uppercase: true
},
zip: Number
}
});
It seems like the only good reason to use a separate Schema is if you absolutely need the embedded data to have an _id attribute and you don't need to add additional validation or alteration options to any of the properties. If you need an _id, I'm guessing you should probably not be embedding the data, but saving it as a separate object and making a reference.
I'll keep updating this as I discover new information and best practices.
Update #2:
If you want to include validation to the embedded document, such as making the address property required, you're going to have to do it separately, as outlined in this very good blog post about it.

Can we embed the geo location coordinates inside another array or does it have to be at the base of the object?

An example will make the question more clear.
var UserSchema =
mongoose.Schema({
name: String,
uniqueIdentifier: String,
phoneNumber: String,
gpsModuleId: String,
userType: String,
groupInfo: {
groupDisclosureFlag: Boolean,
groupContacts: []
},
tenantId: String,
notificationStore: String,
latLongInfo: [{
loc: {
type: String,
coordinates: []
},
date: Date,
isAnchor: Boolean,
isPOT: Boolean,
isStopTracking: Boolean,
signalType: String
}]
});
This is my mongoose schema. Can I query using the mongodb default geo location queries with the same hierarchy?
This is totally acceptable, and will work fine with the 2dsphere index. You can check out https://jira.mongodb.org/browse/SERVER-8907. MongoDB 2.6.X will release with support for Geometry Collections, which are a native GeoJSON type similar to what you are doing here, but there shouldn't be any significant difference (performance or flexibility wise) between the two.

Resources