Complex query with mongoose, including subdocuments, near condition, - node.js

I have a very (at least for me) complex query using Mongoose.
First of all my schema:
var ObjectSchema = new Schema({
pickupStartDate: {type: Date, required: true, default: Date},
pickupEndDate: {type: Date, required: true, default: Date},
...
handoverStartDate: {type: Date, required: true, default: Date},
handoverEndDate: {type: Date, required: true, default: Date},
...
});
By using the "plugin mechanism" my Object has two addresses (called pickupAddress and handoverAddress. The address looks like that:
var name = 'address';
var obj = {};
obj[name] = {
street: String,
zipCode: String,
city: String,
state: String,
country: String,
loc: {type: [Number], index: '2dsphere'}
};
schema.add(obj);
And the other schema:
var TripSchema = new Schema({
startDate: {type: Date, required: true, default: Date},
endDate: {type: Date, required: true, default: Date},
handoverRadius: {type: Number, required: true}
});
It has an address, too (using plugin mechanism again).
I want the following query:
Find all "objects" which "fit" to my trip.
"Fit" means:
handoverStartDate >= trip.startDate
handoverEndDate <= trip.endDate
`handoverAddress is near trip.address
...
I thought this would be a good approach:
ObjectSchema
.find()
.and([
{ handoverStartDate: {$gte: trip.startDate}},
{ handoverEndDate: {$lte: trip.endDate}},
{ 'handoverAddress.loc': {$near: {
'$maxDistance': 10 * 1000,
'$center': {
type: 'Point',
coordinates: trip.address.loc
}
}}}
])
.exec(function(err, cdObjects) {
console.log(err);
console.log(cdObjects);
});
But this leads to the following error:
{ message: 'Cast to number failed for value "[object Object]" at path "handoverAddress.loc"'.
I guess because of 'handoverAddress.loc'. But I'm not sure how to specify that as it has to be a string (because it's a subdocument).

You don't need the and. try
ObjectModel.find({
handoverStartDate: {$gte: trip.startDate},
handoverEndDate: {$lte: trip.endDate},
'handoverAddress.loc': {
$near: {
$geometry: {
type: "Point",
coordinates: trip.address.loc
},
$maxDistance: 10 * 1000
}
})
Make sure trip is defined as a variable and that startDate,endDate, and address are all defined properties fitting your expectations.

This is how it worked for me:
ObjectSchema
.where('handoverStartDate').gte(trip.startDate)
.where('handoverEndDate').lte(trip.endDate)
.where('handoverAddress.loc').near({
center: {
type: 'Point',
coordinates: trip.address.loc
},
maxDistance: 10 * 1000
});

Related

Mongoose Geolocation set default coordinates

I am trying to create mongoose geolocation records with default coordinates. I am able to set the default values so it they are serialized, but they are not really committed to the database so geospatial queries won't work. Here is what I am doing (simplified)
const Place = new mongoose.Schema({
name: { type: String, required: true },
location: {
type: {
type: String,
enum: ['Point'],
required: true,
default: 'Point'
},
coordinates: {
type: [Number],
required: true,
default: function () {
return [0, 0] // Just 0, 0 for the example
}
}
}
When I pull up the record in the mongo shell, there is no location key at all.
i don't know more efficient way
but try this :
//pass data like this in your controller
Store.create({name, lat, lon})
make your model like this
const Place = new mongoose.Schema({
name: { type: String, required: true },
lat: { type: String},
lon: { type: String},
location: {
type: {
type: String,
enum: ['Point'],
},
coordinates: {
type: [Number],
index: '2dsphere'
}
}
// run before saving documents
Place.pre('save', function(next) {
this.location = {
type: 'Point',
coordinates: [this.lat, this.lon]
};
next();
});

How to have relate two collections that have a one-to-one relationship

I have 0 experience with NoSQL databases and it is hard to not come up with a "SQL solution" to this problem. I have a Restaurant, which obviously has an address. My first idea was to simply put a lot of fields such as country, city, zip, line1, etc... However I thought that referencing it to an Address document, giving me the flexibility to easily change the structure of Addresses, so after a little bit of research I came up with this:
var RestaurantSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
address: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Address',
required: true
},
// a few more fields
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now},
});
var AddressSchema = new mongoose.Schema({
restaurant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Restaurant'
},
line1: {
type: String,
required: true
},
line2: {
type: String,
}
// etc
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now},
});
My question comes after wondering how I would do if I wanted to retrieve all restaurants from a city, for example, I'd do something like find('Houston') and then get each Restaurant from each id referenced by the Addresses retrieved?
I feel like there's a better way to do this but at the moment I don't even know what else to search trying to find an answer.
You can make your address schema like this
var AddressSchema = new mongoose.Schema({
restaurant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Restaurant'
},
city: {
type: String,
required: true
},
line1: {
type: String,
}
// etc
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now},
});
Ques: how I would do if I wanted to retrieve all restaurants from a city?
Ans: For this you can use populate of moongose
Ex:
var addressModule=require('addressSchema')
addressModule.find({city:'Houston'}).populate({'path':'restaurant','model':'restaurantSchema'})
Result:
[
{
restaurant:{
name:"ABC",
address:"123"
},
city:"Houston",
line1:"xxx"
},
{
restaurant:{
name:"DEF",
address:"233"
},
city:"Houston",
line1:"xxx"
}
]

MongoError: can't get query executor with geoNear Mongoose

I am trying to get results from DB using geoNear in Moongose but it is giving "can't get query executor" error.
// Schema
let dealSchema = new Schema({
userId: {type: String, required: true},
images: {type: [String], required: true},
location: {
lng: {type: Number},
lat: {type: Number},
name: {type: String, required: true}
},
description: {type: String, required: false, default: ""},
expiredReports: {type: [String], required: false},
inappropriateReports: {type: [String], required: false},
likes: {type: [String], required: false},
dislikes: {type: [String], required: false},
comments: {type: [commentSchema], required: false},
creationDate: {type: Date, default: new Date()},
updationDate: {type: Date, default: new Date()}
}, {collection: TableName.DEAL});
function getAll(radius) {
var point = {type: "Point", coordinates: [9, 9]};
let distance = parseInt(radius);
if (isNaN(distance)) {
return callback(new Error("Invalid Radius"), null)
}
var geoOptions = {
spherical: true,
maxDistance: distance,
num: 10
};
DealData.geoNear(point, geoOptions, function (err, docs, stats) {
console.log(docs);
}
);
}
This is my model of Mongoose and My code to get data from Database.
geoNear method requires 2dsphere index, after creating that index you'll be able to querying data by coords.
Example:
var mySchema = new mongoose.Schema({
name: {type: String, required: true},
location: {type: [Number], index: '2dsphere'}
});
The first item of the location field is longtitude and the second one is latitude

Schema Association in Mongoose

I have 2 models:
Here is the User Model:
const userSchema = new mongoose.Schema({
email: { type: String, unique: true, required: true },
password: { type: String, required: true },
passwordResetToken: String,
passwordResetExpires: Date,
facebook: String,
twitter: String,
tokens: Array,
profile: {
name: String,
gender: String,
location: String,
website: String,
picture: String
}
}, { timestamps: true });
And here is the Revive Model:
const reviveSchema = new mongoose.Schema({
reviveShowName: {type: String, required: true},
reviveTitle: {type: String, required: true},
reviveCategory: {type: String, required: true},
reviveGoal: {type: Number, required: true},
revivePhoto: {type: String, required: true},
reviveVideo: {type: String},
reviveStory: {type: String},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
name: String
}
}, { timestamps: true });
In the Revive model, I'm trying to the reference the author and get the author's id and that works... How do I also get the name from profiles -> name...? Clearly name: String is wrong...
Mongoose relations work, based on the ref and type value of the nested object. In your case you have associated the id property of author to point to the User model.
If you want to populate the author with the user information, you should just do :
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
Then in your query you just use populate
Revive.find({})
.populate( 'author' )
.exec( function( error, docs ) {
console.log( docs ); // will have `[{author:{profile:{...}}}]` data
} );

How to update a subdocument inside another subdocument in mongoose using atomic query?

My Mongoose schema looks as follows:
var MousePos = Schema({
x: { type: Number},
y: { type: Number},
clickCount: {type: Number, default:0}
});
var MouseTrackInfo = Schema({
pos:{ type: [MousePos], default:[]},
element: {type: String, index:true},
clickCount:{ type: Number, default: 0 },
timeSpent: { type: Number, default: 0 }
});
var MouseTrackSchema = Schema({
pageUrl: { type: String},
applicationId: { type: String, index: true },
mouseTrackLog: { type: [MouseTrackInfo], default:[]},
urlId: {type: String, index: true}
});
Now what i want to do is to update clickCount in pos subdocument when element, x and y values are specified.
I tried a number of approaches, but nothings seems to be working. Moreover i can't use two $ operator in my query too. Suggestions will be welcome. I want to make this query as atomic as possible.
I was not able to figure out a reliable way so i changed my schema to following:
var MouseTrackInfo = Schema({
element: {type: String, index:true},
clickCount:{ type: Number, default: 0 },
timeSpent: { type: Number, default: 0 },
x: { type: Number, default:0},
y: { type: Number, default: 0},
identifier: {type: String}
});
var MouseTrackSchema = Schema({
pageUrl: { type: String},
applicationId: { type: String, index: true },
mouseTrackLog: { type: [MouseTrackInfo], default:[]},
urlId: {type: String, index: true}
});
MouseTrackSchema.methods.incrementClickCount = function(element,pageX,pageY,cb) {
var parent = this;
this.model('MouseTrackModel').update(
{
'applicationId':this.applicationId,
'urlId':this.urlId,
'mouseTrackLog.identifier': element+"X"+pageX+"Y"+pageY
},
{
'$inc':{'mouseTrackLog.$.clickCount':1}
},
{
'new': true
},
cb);
}
This allowed my execute a atomic query. Any other solution are welcome.

Resources