Merge pull request #1108 from strongloop/fix_geo_2.x

Fix the bug when near filter is used
This commit is contained in:
Amirali Jafarian 2016-10-13 10:39:15 -04:00 committed by GitHub
commit 8caf4c8327
2 changed files with 170 additions and 157 deletions

View File

@ -1798,174 +1798,174 @@ DataAccessObject.find = function find(query, options, cb) {
var near = query && geo.nearFilter(query.where); var near = query && geo.nearFilter(query.where);
var supportsGeo = !!connector.buildNearFilter; var supportsGeo = !!connector.buildNearFilter;
if (near) { if (query.where && near && !supportsGeo) {
if (supportsGeo) { // do in memory query
// convert it // using all documents
connector.buildNearFilter(query, near); // TODO [fabien] use default scope here?
} else if (query.where) {
// do in memory query
// using all documents
// TODO [fabien] use default scope here?
if (options.notify === false) { if (options.notify === false) {
queryGeo(query); queryGeo(query);
} else { } else {
withNotifyGeo(); withNotifyGeo();
}
function withNotifyGeo() {
var context = {
Model: self,
query: query,
hookState: hookState,
options: options,
};
self.notifyObserversOf('access', context, function(err, ctx) {
if (err) return cb(err);
queryGeo(ctx.query);
});
}
function queryGeo(query) {
function geoCallback(err, data) {
var memory = new Memory();
var modelName = self.modelName;
if (err) {
cb(err);
} else if (Array.isArray(data)) {
memory.define({
properties: self.dataSource.definitions[self.modelName].properties,
settings: self.dataSource.definitions[self.modelName].settings,
model: self,
});
data.forEach(function(obj) {
memory.create(modelName, obj, options, function() {
// noop
});
});
// FIXME: apply "includes" and other transforms - see allCb below
memory.all(modelName, query, options, cb);
} else {
cb(null, []);
}
}
if (connector.all.length === 4) {
connector.all(self.modelName, {}, options, geoCallback);
} else {
connector.all(self.modelName, {}, geoCallback);
}
// already handled
return cb.promise;
}
} }
}
var allCb = function(err, data) { function withNotifyGeo() {
if (!err && Array.isArray(data)) { var context = {
async.map(data, function(item, next) { Model: self,
var Model = self.lookupModel(item); query: query,
var obj = new Model(item, { fields: query.fields, applySetters: false, persisted: true }); hookState: hookState,
options: options,
if (query && query.include) { };
if (query.collect) { self.notifyObserversOf('access', context, function(err, ctx) {
// The collect property indicates that the query is to return the
// standalone items for a related model, not as child of the parent object
// For example, article.tags
obj = obj.__cachedRelations[query.collect];
if (obj === null) {
obj = undefined;
}
} else {
// This handles the case to return parent items including the related
// models. For example, Article.find({include: 'tags'}, ...);
// Try to normalize the include
var includes = Inclusion.normalizeInclude(query.include || []);
includes.forEach(function(inc) {
var relationName = inc;
if (utils.isPlainObject(inc)) {
relationName = Object.keys(inc)[0];
}
// Promote the included model as a direct property
var included = obj.__cachedRelations[relationName];
if (Array.isArray(included)) {
included = new List(included, null, obj);
}
if (included) obj.__data[relationName] = included;
});
delete obj.__data.__cachedRelations;
}
}
if (obj !== undefined) {
if (options.notify === false) {
next(null, obj);
} else {
context = {
Model: Model,
instance: obj,
isNewInstance: false,
hookState: hookState,
options: options,
};
Model.notifyObserversOf('loaded', context, function(err) {
if (err) return next(err);
next(null, obj);
});
}
} else {
next();
}
},
function(err, results) {
if (err) return cb(err); if (err) return cb(err);
queryGeo(ctx.query);
// When applying query.collect, some root items may not have
// any related/linked item. We store `undefined` in the results
// array in such case, which is not desirable from API consumer's
// point of view.
results = results.filter(isDefined);
if (data && data.countBeforeLimit) {
results.countBeforeLimit = data.countBeforeLimit;
}
if (!supportsGeo && near) {
results = geo.filter(results, near);
}
cb(err, results);
}); });
} else {
cb(err, data || []);
} }
};
if (options.notify === false) { function queryGeo(query) {
if (connector.all.length === 4) { function geoCallback(err, data) {
connector.all(self.modelName, query, options, allCb); var memory = new Memory();
} else { var modelName = self.modelName;
connector.all(self.modelName, query, allCb);
if (err) {
cb(err);
} else if (Array.isArray(data)) {
memory.define({
properties: self.dataSource.definitions[self.modelName].properties,
settings: self.dataSource.definitions[self.modelName].settings,
model: self,
});
data.forEach(function(obj) {
memory.create(modelName, obj, options, function() {
// noop
});
});
// FIXME: apply "includes" and other transforms - see allCb below
memory.all(modelName, query, options, cb);
} else {
cb(null, []);
}
}
if (connector.all.length === 4) {
connector.all(self.modelName, {}, options, geoCallback);
} else {
connector.all(self.modelName, {}, geoCallback);
}
// already handled
return cb.promise;
} }
} else { } else {
var context = { if (near && supportsGeo) {
Model: this, connector.buildNearFilter(query, near);
query: query, }
hookState: hookState,
options: options,
};
this.notifyObserversOf('access', context, function(err, ctx) {
if (err) return cb(err);
connector.all.length === 4 ? var allCb = function(err, data) {
connector.all(self.modelName, ctx.query, options, allCb) : if (!err && Array.isArray(data)) {
connector.all(self.modelName, ctx.query, allCb); async.map(data, function(item, next) {
}); var Model = self.lookupModel(item);
var obj = new Model(item, { fields: query.fields, applySetters: false, persisted: true });
if (query && query.include) {
if (query.collect) {
// The collect property indicates that the query is to return the
// standalone items for a related model, not as child of the parent object
// For example, article.tags
obj = obj.__cachedRelations[query.collect];
if (obj === null) {
obj = undefined;
}
} else {
// This handles the case to return parent items including the related
// models. For example, Article.find({include: 'tags'}, ...);
// Try to normalize the include
var includes = Inclusion.normalizeInclude(query.include || []);
includes.forEach(function(inc) {
var relationName = inc;
if (utils.isPlainObject(inc)) {
relationName = Object.keys(inc)[0];
}
// Promote the included model as a direct property
var included = obj.__cachedRelations[relationName];
if (Array.isArray(included)) {
included = new List(included, null, obj);
}
if (included) obj.__data[relationName] = included;
});
delete obj.__data.__cachedRelations;
}
}
if (obj !== undefined) {
if (options.notify === false) {
next(null, obj);
} else {
context = {
Model: Model,
instance: obj,
isNewInstance: false,
hookState: hookState,
options: options,
};
Model.notifyObserversOf('loaded', context, function(err) {
if (err) return next(err);
next(null, obj);
});
}
} else {
next();
}
},
function(err, results) {
if (err) return cb(err);
// When applying query.collect, some root items may not have
// any related/linked item. We store `undefined` in the results
// array in such case, which is not desirable from API consumer's
// point of view.
results = results.filter(isDefined);
if (data && data.countBeforeLimit) {
results.countBeforeLimit = data.countBeforeLimit;
}
if (!supportsGeo && near) {
results = geo.filter(results, near);
}
cb(err, results);
});
} else {
cb(err, data || []);
}
};
if (options.notify === false) {
if (connector.all.length === 4) {
connector.all(self.modelName, query, options, allCb);
} else {
connector.all(self.modelName, query, allCb);
}
} else {
var context = {
Model: this,
query: query,
hookState: hookState,
options: options,
};
this.notifyObserversOf('access', context, function(err, ctx) {
if (err) return cb(err);
connector.all.length === 4 ?
connector.all(self.modelName, ctx.query, options, allCb) :
connector.all(self.modelName, ctx.query, allCb);
});
}
return cb.promise;
} }
return cb.promise;
}; };
function isDefined(value) { function isDefined(value) {

View File

@ -86,6 +86,19 @@ module.exports = function(dataSource, should, connectorCapabilities) {
}); });
}); });
it('triggers correct hooks when near filter is used', function(done) {
monitorHookExecution();
var query = { where:
{ location: { near: '10,20', maxDistance: '10', unit: 'meters' }},
};
TestModel.find(query, function(err, list) {
if (err) return done(err);
hookMonitor.names.should.eql(['access']);
done();
});
});
it('should not trigger hooks, if notify is false', function(done) { it('should not trigger hooks, if notify is false', function(done) {
monitorHookExecution(); monitorHookExecution();
TestModel.find( TestModel.find(