Merge branch 'merge-inclusion' of https://github.com/walkonsocial/loopback-datasource-juggler into walkonsocial-merge-inclusion

This commit is contained in:
Raymond Feng 2015-04-24 16:12:38 -07:00
commit 3aa90751be
3 changed files with 240 additions and 4 deletions

View File

@ -60,7 +60,7 @@ ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefres
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or refresh is required
var params = mergeQuery(actualCond, scopeParams);
var params = mergeQuery(actualCond, scopeParams, {nestedInclude: true});
var targetModel = this.targetModel(receiver);
targetModel.find(params, function (err, data) {
if (!err && saveOnCache) {

View File

@ -9,6 +9,7 @@ exports.defineCachedRelations = defineCachedRelations;
exports.sortObjectsByIds = sortObjectsByIds;
exports.setScopeValuesFromWhere = setScopeValuesFromWhere;
exports.mergeQuery = mergeQuery;
exports.mergeIncludes = mergeIncludes;
exports.createPromiseCallback = createPromiseCallback
var traverse = require('traverse');
@ -53,6 +54,87 @@ function setScopeValuesFromWhere(data, where, targetModel) {
}
}
/**
* Merge include options of default scope with runtime include option.
* exhibits the _.extend behaviour. Property value of source overrides
* property value of destination if property name collision occurs
* @param {String|Array|Object} destination The default value of `include` option
* @param {String|Array|Object} source The runtime value of `include` option
* @returns {Object}
*/
function mergeIncludes(destination, source) {
var destArray = convertToArray(destination);
var sourceArray = convertToArray(source);
if (destArray.length === 0) {
return sourceArray;
}
if (sourceArray.length === 0) {
return destArray;
}
var relationNames = [];
var resultArray = [];
for (var j in sourceArray) {
var sourceEntry = sourceArray[j];
var sourceEntryRelationName = (typeof (sourceEntry.rel || sourceEntry.relation) === 'string') ?
sourceEntry.relation : Object.keys(sourceEntry)[0];
relationNames.push(sourceEntryRelationName);
resultArray.push(sourceEntry);
}
for (var i in destArray) {
var destEntry = destArray[i];
var destEntryRelationName = (typeof (destEntry.rel || destEntry.relation) === 'string') ?
destEntry.relation : Object.keys(destEntry)[0];
if (relationNames.indexOf(destEntryRelationName) === -1) {
resultArray.push(destEntry);
}
}
return resultArray;
}
/**
* Converts input parameter into array of objects which wraps the value.
* "someValue" is converted to [{"someValue":true}]
* ["someValue"] is converted to [{"someValue":true}]
* {"someValue":true} is converted to [{"someValue":true}]
* @param {String|Array|Object} param - Input parameter to be converted
* @returns {Array}
*/
function convertToArray(include) {
if (typeof include === 'string') {
var obj = {};
obj[include] = true;
return [obj];
} else if (isPlainObject(include)) {
//if include is of the form - {relation:'',scope:''}
if (include.rel || include.relation) {
return [include];
}
// Build an array of key/value pairs
var newInclude = [];
for (var key in include) {
var obj = {};
obj[key] = include[key];
newInclude.push(obj);
}
return newInclude;
} else if (Array.isArray(include)) {
var normalized = [];
for (var i in include) {
var includeEntry = include[i];
if (typeof includeEntry === 'string') {
var obj = {};
obj[includeEntry] = true;
normalized.push(obj)
}
else{
normalized.push(includeEntry);
}
}
return normalized;
}
return [];
}
/*!
* Merge query parameters
* @param {Object} base The base object to contain the merged results
@ -81,9 +163,19 @@ function mergeQuery(base, update, spec) {
if (!base.include) {
base.include = update.include;
} else {
var saved = base.include;
base.include = {};
base.include[update.include] = saved;
if (spec.nestedInclude === true){
//specify nestedInclude=true to force nesting of inclusions on scoped
//queries. e.g. In physician.patients.getAsync({include: 'address'}),
//inclusion should be on patient model, not on physician model.
var saved = base.include;
base.include = {};
base.include[update.include] = saved;
}
else{
//default behaviour of inclusion merge - merge inclusions at the same
//level. - https://github.com/strongloop/loopback-datasource-juggler/pull/569#issuecomment-95310874
base.include = mergeIncludes(base.include, update.include);
}
}
}

View File

@ -3,6 +3,7 @@ var utils = require('../lib/utils');
var fieldsToArray = utils.fieldsToArray;
var removeUndefined = utils.removeUndefined;
var mergeSettings = utils.mergeSettings;
var mergeIncludes = utils.mergeIncludes;
var sortObjectsByIds = utils.sortObjectsByIds;
describe('util.fieldsToArray', function () {
@ -218,3 +219,146 @@ describe('sortObjectsByIds', function () {
});
});
describe('util.mergeIncludes', function () {
function checkInputOutput(baseInclude, updateInclude, expectedInclude) {
var mergedInclude = mergeIncludes(baseInclude, updateInclude);
should.deepEqual(mergedInclude, expectedInclude,
'Merged include should match the expectation');
}
it('Merge string values to object', function () {
var baseInclude = 'relation1';
var updateInclude = 'relation2';
var expectedInclude = [
{relation2: true},
{relation1: true}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string & array values to object', function () {
var baseInclude = 'relation1';
var updateInclude = ['relation2'];
var expectedInclude = [
{relation2: true},
{relation1: true}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string & object values to object', function () {
var baseInclude = ['relation1'];
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
{relation2: 'relation2Include'},
{relation1: true}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge array & array values to object', function () {
var baseInclude = ['relation1'];
var updateInclude = ['relation2'];
var expectedInclude = [
{relation2: true},
{relation1: true}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge array & object values to object', function () {
var baseInclude = ['relation1'];
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
{relation2: 'relation2Include'},
{relation1: true}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge object & object values to object', function () {
var baseInclude = {relation1: 'relation1Include'};
var updateInclude = {relation2: 'relation2Include'};
var expectedInclude = [
{relation2: 'relation2Include'},
{relation1: 'relation1Include'}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Override property collision with update value', function () {
var baseInclude = {relation1: 'baseValue'};
var updateInclude = {relation1: 'updateValue'};
var expectedInclude = [
{relation1: 'updateValue'}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string includes & include with relation syntax properly',
function () {
var baseInclude = 'relation1';
var updateInclude = {relation: 'relation1'};
var expectedInclude = [
{relation: 'relation1'}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge string includes & include with scope properly', function () {
var baseInclude = 'relation1';
var updateInclude = {
relation: 'relation1',
scope: {include: 'relation2'}
};
var expectedInclude = [
{relation: 'relation1', scope: {include: 'relation2'}}
];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge includes with and without relation syntax properly',
function () {
//w & w/o relation syntax - no collision
var baseInclude = ['relation2'];
var updateInclude = {
relation: 'relation1',
scope: {include: 'relation2'}
};
var expectedInclude = [{
relation: 'relation1',
scope: {include: 'relation2'}
}, {relation2: true}];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
//w & w/o relation syntax - collision
baseInclude = ['relation1'];
updateInclude = {relation: 'relation1', scope: {include: 'relation2'}};
expectedInclude =
[{relation: 'relation1', scope: {include: 'relation2'}}];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
//w & w/o relation syntax - collision
baseInclude = {relation: 'relation1', scope: {include: 'relation2'}};
updateInclude = ['relation1'];
expectedInclude = [{relation1: true}];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
it('Merge includes with mixture of strings, arrays & objects properly', function () {
var baseInclude = ['relation1', {relation2: true},
{relation: 'relation3', scope: {where: {id: 'some id'}}},
{relation: 'relation5', scope: {where: {id: 'some id'}}}
];
var updateInclude = ['relation4', {relation3: true},
{relation: 'relation2', scope: {where: {id: 'some id'}}}];
var expectedInclude = [{relation4: true}, {relation3: true},
{relation: 'relation2', scope: {where: {id: 'some id'}}},
{relation1: true},
{relation: 'relation5', scope: {where: {id: 'some id'}}}];
checkInputOutput(baseInclude, updateInclude, expectedInclude);
});
});