-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Geosearch #1560
Geosearch #1560
Changes from 3 commits
55a66d5
b1837da
98c13d1
d71d589
197ba74
43355d4
51345f1
1e258ca
beb2894
7e4c72c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1666,6 +1666,74 @@ Model.aggregate = function aggregate () { | |
return aggregate.exec(callback); | ||
} | ||
|
||
/** | ||
* Implements $geoSearch functionality for Mongoose | ||
* | ||
* ####Example: | ||
* | ||
* var options = { near: [10, 10], maxDistance: 5 }; | ||
* Locations.geoSearch({ type : "house" }, options, function(err, res) { | ||
* console.log(res); | ||
* }); | ||
* | ||
* ####Options: | ||
* - `near` {Array} x,y point to search for | ||
* - `maxDistance` {Number} the maximum distance from the point near that a result can be | ||
* - `limit` {Number} The maximum number of results to return | ||
* - `lean` {Boolean} return the raw object instead of the Mongoose Model | ||
* | ||
* @param {Object} condition an object that specifies the match condition (required) | ||
* @param {Object} options for the geoSearch, some (near, maxDistance) are required | ||
* @see http://docs.mongodb.org/manual/reference/command/geoSearch/ | ||
* @see http://docs.mongodb.org/manual/core/geohaystack/ | ||
* @api public | ||
*/ | ||
|
||
Model.geoSearch = function (conditions, options, callback) { | ||
if ('function' == typeof options) { | ||
callback = options; | ||
options = {}; | ||
} | ||
if (!callback || 'function' != typeof callback) { | ||
throw new Error("Must pass a callback to geoSearch"); | ||
} | ||
|
||
if (conditions == undefined || !utils.isObject(conditions)) { | ||
return callback(new Error("Must pass conditions to geoSearch")); | ||
} | ||
|
||
if (!options.near) { | ||
return callback(new Error("Must specify the near option in geoSearch")); | ||
} | ||
|
||
if (!Array.isArray(options.near)) { | ||
return callback(new Error("near option must be an array [x, y]")); | ||
} | ||
|
||
|
||
// send the conditions in the options object | ||
options.search = conditions; | ||
var self = this; | ||
|
||
return this.collection.geoHaystackSearch(options.near[0], options.near[1], options, function (err, res) { | ||
if (err || res.errmsg) return callback(err, res); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. err will not exist if res.errmsg exists. The way that the driver handles these admin commands, it basically just passes back the raw results and doesn't do any error checking. I was trying to decide between just passing the res object as the error if res.errmsg exists, but decided to try it like this first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah we should follow the node paradigm if at all possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, to confirm: we will pass the res object back as the error if res.errmsg exists? |
||
|
||
if (!options.lean) { | ||
var count = res.results.length; | ||
for (var i=0; i < res.results.length; i++) { | ||
var temp = res.results[i]; | ||
res.results[i] = new self(); | ||
res.results[i].init(temp, {}, function (err) { | ||
if (err) return callback(err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should guard against potentially calling the |
||
--count || callback(err, res); | ||
}); | ||
} | ||
} else { | ||
callback(err, res); | ||
} | ||
}); | ||
}; | ||
|
||
/** | ||
* Populates document references. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
|
||
var start = require('./common') | ||
, assert = require('assert') | ||
, mongoose = start.mongoose | ||
, random = require('../lib/utils').random | ||
, Schema = mongoose.Schema | ||
, DocumentObjectId = mongoose.Types.ObjectId | ||
|
||
/** | ||
* Setup | ||
*/ | ||
|
||
var schema = new Schema({ | ||
pos : [Number], | ||
complex : {}, | ||
type: String | ||
}); | ||
|
||
schema.index({ "pos" : "geoHaystack", type : 1},{ bucketSize : 1}); | ||
|
||
mongoose.model('Geo', schema); | ||
|
||
describe('model', function(){ | ||
describe.only('geoSearch', function () { | ||
it('works', function (done) { | ||
|
||
var db = start(); | ||
var Geo = db.model('Geo'); | ||
assert.ok(Geo.geoSearch instanceof Function); | ||
|
||
var geos = []; | ||
geos[0] = new Geo({ pos : [10,10], type : "place"}); | ||
geos[1] = new Geo({ pos : [15,5], type : "place"}); | ||
geos[2] = new Geo({ pos : [20,15], type : "house"}); | ||
geos[3] = new Geo({ pos : [1,-1], type : "house"}); | ||
var count = geos.length; | ||
|
||
for (var i=0; i < geos.length; i++) { | ||
geos[i].save(function () { | ||
--count || next(); | ||
}); | ||
} | ||
|
||
function next() { | ||
Geo.geoSearch({ type : "place" }, { near : [9,9], maxDistance : 5 }, function (err, results, stats) { | ||
assert.ifError(err); | ||
assert.equal(1, results.results.length); | ||
assert.equal(1, results.ok); | ||
|
||
assert.equal(results.results[0].type, 'place'); | ||
assert.equal(results.results[0].pos.length, 2); | ||
assert.equal(results.results[0].pos[0], 10); | ||
assert.equal(results.results[0].pos[1], 10); | ||
assert.equal(results.results[0].id, geos[0].id); | ||
assert.ok(results.results[0] instanceof Geo); | ||
Geo.remove(function () { | ||
db.close(); | ||
done(); | ||
}); | ||
}); | ||
} | ||
}); | ||
it('works with lean', function (done) { | ||
|
||
var db = start(); | ||
var Geo = db.model('Geo'); | ||
assert.ok(Geo.geoSearch instanceof Function); | ||
|
||
var geos = []; | ||
geos[0] = new Geo({ pos : [10,10], type : "place"}); | ||
geos[1] = new Geo({ pos : [15,5], type : "place"}); | ||
geos[2] = new Geo({ pos : [20,15], type : "house"}); | ||
geos[3] = new Geo({ pos : [1,-1], type : "house"}); | ||
var count = geos.length; | ||
|
||
for (var i=0; i < geos.length; i++) { | ||
geos[i].save(function () { | ||
--count || next(); | ||
}); | ||
} | ||
|
||
function next() { | ||
Geo.geoSearch({ type : "place" }, { near : [9,9], maxDistance : 5, lean : true }, function (err, results, stats) { | ||
assert.ifError(err); | ||
assert.equal(1, results.results.length); | ||
assert.equal(1, results.ok); | ||
|
||
assert.equal(results.results[0].type, 'place'); | ||
assert.equal(results.results[0].pos.length, 2); | ||
assert.equal(results.results[0].pos[0], 10); | ||
assert.equal(results.results[0].pos[1], 10); | ||
assert.equal(results.results[0]._id, geos[0].id); | ||
assert.strictEqual(results.results[0].id, undefined); | ||
assert.ok(!(results.results[0] instanceof Geo)); | ||
Geo.remove(function () { | ||
db.close(); | ||
done(); | ||
}); | ||
}); | ||
} | ||
}); | ||
it('throws the correct error messages', function (done) { | ||
|
||
var db = start(); | ||
var Geo = db.model('Geo'); | ||
var g = new Geo({ pos : [10,10], type : "place"}); | ||
g.save(function() { | ||
var threw = false; | ||
Geo.geoSearch([], {}, function (e) { | ||
assert.ok(e); | ||
assert.equal(e.message, "Must pass conditions to geoSearch"); | ||
|
||
Geo.geoSearch({ type : "test"}, {}, function (e) { | ||
assert.ok(e); | ||
assert.equal(e.message, "Must specify the near option in geoSearch"); | ||
|
||
Geo.geoSearch({ type : "test" }, { near : "hello" }, function (e) { | ||
assert.ok(e); | ||
assert.equal(e.message, "near option must be an array [x, y]"); | ||
|
||
try { | ||
Geo.geoSearch({ type : "test" }, { near : [1,2] }, []); | ||
} catch(e) { | ||
threw = true; | ||
assert.ok(e); | ||
assert.equal(e.message, "Must pass a callback to geoSearch"); | ||
} | ||
|
||
assert.ok(threw); | ||
threw = false; | ||
|
||
try { | ||
Geo.geoSearch({ type : "test" }, { near : [1,2] }); | ||
} catch(e) { | ||
threw = true; | ||
assert.ok(e); | ||
assert.equal(e.message, "Must pass a callback to geoSearch"); | ||
} | ||
|
||
assert.ok(threw); | ||
Geo.geoSearch({ type : "test" }, { near : [1,2] }, function (err, res) { | ||
assert.ifError(err); | ||
assert.ok(res); | ||
|
||
assert.equal(res.ok, 0); | ||
assert.equal(res.errmsg, "exception: maxDistance needs a number"); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could drop the
!callback
since the other check covers it.