Merge pull request #1642 from strongloop/fix-4028
Report errors for missing id properties for include
This commit is contained in:
commit
f1f535846e
|
@ -14,6 +14,7 @@ var isPlainObject = utils.isPlainObject;
|
||||||
var defineCachedRelations = utils.defineCachedRelations;
|
var defineCachedRelations = utils.defineCachedRelations;
|
||||||
var uniq = utils.uniq;
|
var uniq = utils.uniq;
|
||||||
var idName = utils.idName;
|
var idName = utils.idName;
|
||||||
|
var debug = require('debug')('loopback:include');
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Normalize the include to be an array
|
* Normalize the include to be an array
|
||||||
|
@ -172,6 +173,7 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
include = normalizeInclude(include);
|
include = normalizeInclude(include);
|
||||||
|
debug('include: %j', include);
|
||||||
|
|
||||||
// Find the limit of items for `inq`
|
// Find the limit of items for `inq`
|
||||||
var inqLimit = 256;
|
var inqLimit = 256;
|
||||||
|
@ -181,8 +183,14 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async.each(include, function(item, callback) {
|
async.each(include, function(item, callback) {
|
||||||
processIncludeItem(objects, item, options, callback);
|
try {
|
||||||
|
processIncludeItem(objects, item, options, callback);
|
||||||
|
} catch (err) {
|
||||||
|
// async does not catch the error and report to the outer callback
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
|
debug(err, objects);
|
||||||
cb && cb(err, objects);
|
cb && cb(err, objects);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -293,6 +301,7 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
cb(new Error(g.f('Relation "%s" is not defined for %s model', relationName, self.modelName)));
|
cb(new Error(g.f('Relation "%s" is not defined for %s model', relationName, self.modelName)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
debug('Relation: %j', relation);
|
||||||
var polymorphic = relation.polymorphic;
|
var polymorphic = relation.polymorphic;
|
||||||
// if (polymorphic && !polymorphic.discriminator) {
|
// if (polymorphic && !polymorphic.discriminator) {
|
||||||
// cb(new Error('Relation "' + relationName + '" is polymorphic but ' +
|
// cb(new Error('Relation "' + relationName + '" is polymorphic but ' +
|
||||||
|
@ -309,6 +318,7 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
|
|
||||||
// Just skip if inclusion is disabled
|
// Just skip if inclusion is disabled
|
||||||
if (relation.options.disableInclude) {
|
if (relation.options.disableInclude) {
|
||||||
|
debug('Relation is disabled from include', relation);
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
// prepare filter and fields for making DB Call
|
// prepare filter and fields for making DB Call
|
||||||
|
@ -870,10 +880,10 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
for (var i = 0; i < objs.length; i++) {
|
for (var i = 0; i < objs.length; i++) {
|
||||||
var obj = objs[i];
|
var obj = objs[i];
|
||||||
if (relation.type === 'belongsTo') {
|
if (relation.type === 'belongsTo') {
|
||||||
if (obj[relation.keyFrom] === null ||
|
if (obj[relation.keyFrom] == null) {
|
||||||
obj[relation.keyFrom] === undefined) {
|
|
||||||
defineCachedRelations(obj);
|
defineCachedRelations(obj);
|
||||||
obj.__cachedRelations[relationName] = null;
|
obj.__cachedRelations[relationName] = null;
|
||||||
|
debug('ID property "%s" is missing in item %j', relation.keyFrom, obj);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,6 +893,8 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
var targetIdStr = targetId.toString();
|
var targetIdStr = targetId.toString();
|
||||||
objTargetIdMap[targetIdStr] = objTargetIdMap[targetIdStr] || [];
|
objTargetIdMap[targetIdStr] = objTargetIdMap[targetIdStr] || [];
|
||||||
objTargetIdMap[targetIdStr].push(obj);
|
objTargetIdMap[targetIdStr].push(obj);
|
||||||
|
} else {
|
||||||
|
debug('ID property "%s" is missing in item %j', relation.keyFrom, obj);
|
||||||
}
|
}
|
||||||
defineCachedRelations(obj);
|
defineCachedRelations(obj);
|
||||||
obj.__cachedRelations[relationName] = null;
|
obj.__cachedRelations[relationName] = null;
|
||||||
|
|
|
@ -10,6 +10,20 @@ module.exports.buildOneToManyIdentityMapWithOrigKeys = buildOneToManyIdentityMap
|
||||||
module.exports.join = join;
|
module.exports.join = join;
|
||||||
module.exports.KVMap = KVMap;
|
module.exports.KVMap = KVMap;
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
|
function getId(obj, idName) {
|
||||||
|
var id = obj && obj[idName];
|
||||||
|
if (id == null) {
|
||||||
|
const msg = util.format('ID property "%s" is missing for included item: %j. ' +
|
||||||
|
'Please make sure `fields` include "%s" if it\'s present in the `filter`',
|
||||||
|
idName, obj, idName);
|
||||||
|
const err = new Error(msg);
|
||||||
|
err.statusCode = 400;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Effectively builds associative map on id -> object relation and stores original keys.
|
* Effectively builds associative map on id -> object relation and stores original keys.
|
||||||
* Map returned in form of object with ids in keys and object as values.
|
* Map returned in form of object with ids in keys and object as values.
|
||||||
|
@ -22,7 +36,7 @@ function buildOneToOneIdentityMapWithOrigKeys(objs, idName) {
|
||||||
var kvMap = new KVMap();
|
var kvMap = new KVMap();
|
||||||
for (var i = 0; i < objs.length; i++) {
|
for (var i = 0; i < objs.length; i++) {
|
||||||
var obj = objs[i];
|
var obj = objs[i];
|
||||||
var id = obj[idName];
|
var id = getId(obj, idName);
|
||||||
kvMap.set(id, obj);
|
kvMap.set(id, obj);
|
||||||
}
|
}
|
||||||
return kvMap;
|
return kvMap;
|
||||||
|
@ -32,7 +46,7 @@ function buildOneToManyIdentityMapWithOrigKeys(objs, idName) {
|
||||||
var kvMap = new KVMap();
|
var kvMap = new KVMap();
|
||||||
for (var i = 0; i < objs.length; i++) {
|
for (var i = 0; i < objs.length; i++) {
|
||||||
var obj = objs[i];
|
var obj = objs[i];
|
||||||
var id = obj[idName];
|
var id = getId(obj, idName);
|
||||||
var value = kvMap.get(id) || [];
|
var value = kvMap.get(id) || [];
|
||||||
value.push(obj);
|
value.push(obj);
|
||||||
kvMap.set(id, value);
|
kvMap.set(id, value);
|
||||||
|
|
|
@ -46,6 +46,15 @@ describe('include', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not return included item if FK is excluded', function(done) {
|
||||||
|
Passport.find({include: 'owner', fields: 'number'}, function(err, passports) {
|
||||||
|
if (err) return done(err);
|
||||||
|
var owner = passports[0].toJSON().owner;
|
||||||
|
should.not.exist(owner);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fetch hasMany relation', function(done) {
|
it('should fetch hasMany relation', function(done) {
|
||||||
User.find({include: 'posts'}, function(err, users) {
|
User.find({include: 'posts'}, function(err, users) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -67,6 +76,14 @@ describe('include', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report errors if the PK is excluded', function(done) {
|
||||||
|
User.find({include: 'posts', fields: 'posts'}, function(err) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.match(/ID property "id" is missing/);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not have changed the __strict flag of the model', function(done) {
|
it('should not have changed the __strict flag of the model', function(done) {
|
||||||
const originalStrict = User.definition.settings.strict;
|
const originalStrict = User.definition.settings.strict;
|
||||||
User.definition.settings.strict = true; // Change to test regression for issue #1252
|
User.definition.settings.strict = true; // Change to test regression for issue #1252
|
||||||
|
@ -579,6 +596,16 @@ describe('include', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not return included item if hasOne is missing the id property', function(done) {
|
||||||
|
User.findOne({include: {relation: 'profile'}, fields: 'name'}, function(err, user) {
|
||||||
|
if (err) return done(err);
|
||||||
|
should.exist(user);
|
||||||
|
// Convert to JSON as the user instance has `profile` as a relational method
|
||||||
|
should.not.exist(user.toJSON().profile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('works when hasMany is called', function(done) {
|
it('works when hasMany is called', function(done) {
|
||||||
User.findOne({include: {relation: 'posts'}}, function(err, user) {
|
User.findOne({include: {relation: 'posts'}}, function(err, user) {
|
||||||
if (err) return done();
|
if (err) return done();
|
||||||
|
@ -1487,6 +1514,7 @@ function setup(done) {
|
||||||
});
|
});
|
||||||
Passport = db.define('Passport', {
|
Passport = db.define('Passport', {
|
||||||
number: String,
|
number: String,
|
||||||
|
expirationDate: Date,
|
||||||
});
|
});
|
||||||
Post = db.define('Post', {
|
Post = db.define('Post', {
|
||||||
title: String,
|
title: String,
|
||||||
|
|
|
@ -21,6 +21,17 @@ describe('include_util', function() {
|
||||||
result.get(22).should.be.ok;
|
result.get(22).should.be.ok;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report errors if id is missing', function() {
|
||||||
|
var objs = [
|
||||||
|
{letter: 'A'},
|
||||||
|
{id: 22, letter: 'B'},
|
||||||
|
];
|
||||||
|
function build() {
|
||||||
|
includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, 'id');
|
||||||
|
}
|
||||||
|
build.should.throw(/ID property "id" is missing/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should overwrite keys in case of collision', function() {
|
it('should overwrite keys in case of collision', function() {
|
||||||
var objs = [
|
var objs = [
|
||||||
{id: 11, letter: 'A'},
|
{id: 11, letter: 'A'},
|
||||||
|
@ -58,6 +69,17 @@ describe('include_util', function() {
|
||||||
result.exist(22).should.be.true;
|
result.exist(22).should.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report errors if id is missing', function() {
|
||||||
|
var objs = [
|
||||||
|
{letter: 'A'},
|
||||||
|
{id: 22, letter: 'B'},
|
||||||
|
];
|
||||||
|
function build() {
|
||||||
|
includeUtils.buildOneToManyIdentityMapWithOrigKeys(objs, 'id');
|
||||||
|
}
|
||||||
|
build.should.throw(/ID property "id" is missing/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should collect keys in case of collision', function() {
|
it('should collect keys in case of collision', function() {
|
||||||
var objs = [
|
var objs = [
|
||||||
{'fk_id': 11, letter: 'A'},
|
{'fk_id': 11, letter: 'A'},
|
||||||
|
|
Loading…
Reference in New Issue