Skip to content

Commit

Permalink
Merge pull request #5 from jura-b/feature/general-get-include-support
Browse files Browse the repository at this point in the history
Find by id support
  • Loading branch information
JonnyBGod authored Nov 25, 2017
2 parents b7d2b87 + 7f10070 commit 16954b1
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 60 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,15 @@ Example of Through Model:

| option | type | description | required |
| ------ | ---- | ----------- | -------- |
|relations| [String] | select relations | false |
|relations| [String, Object] | select relations, can be object with property "name" and "asProperty" | false |
|relations[0].name|String|name of the relation |false|
|relations[0].asProperty|String|rename the property that through model will be injected to, default is name of the through model |false|
|fields| Key/Value Object | similar to filter fields. Key: relation; Value: fields filter. | false |



- By setting relations in model definition it will return the though model for the specified relations by default
- By passing **includeThrough** in you query filter it will override default **fields**
- By passing **includeThrough** in you query filter it will ov erride default **fields**

## License

Expand All @@ -171,4 +175,4 @@ Example of Through Model:
[license-image]: http://img.shields.io/badge/license-MIT-blue.svg
[license-url]: LICENSE
[slack-image]: https://loopback-include-through-mixin.herokuapp.com/badge.svg
[slack-url]: https://loopback-include-through-mixin.herokuapp.com
[slack-url]: https://loopback-include-through-mixin.herokuapp.com
207 changes: 151 additions & 56 deletions include-through.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,177 @@
'use strict';

const _ = require('lodash');

module.exports = function(Model, options) {
var modelHasManyThroughRelations, modelRelations;

Model.on('attached', function() {
var relations = Model.settings.relations || Model.relations;
modelRelations = Model.settings.relations || Model.relations;

if (relations) {
Object.keys(relations).forEach(function(targetModel) {
if (modelRelations) {
modelHasManyThroughRelations = [];
Object.keys(modelRelations).forEach(function(targetModel) {
var type =
(relations[targetModel].modelThrough || relations[targetModel].through) ?
'hasManyThrough' : relations[targetModel].type;
(modelRelations[targetModel].modelThrough || modelRelations[targetModel].through) ?
'hasManyThrough' : modelRelations[targetModel].type;

if (type === 'hasManyThrough') {
Model.afterRemote('prototype.__get__' + targetModel, injectIncludes);
Model.afterRemote('prototype.__create__' + targetModel, injectIncludes);
Model.afterRemote('prototype.__get__' + targetModel, controller);
Model.afterRemote('prototype.__create__' + targetModel, controller);
modelHasManyThroughRelations.push(targetModel);
}
});

if (modelHasManyThroughRelations.length) {
Model.afterRemote('findById', controller);
}
}
});

function injectIncludes(ctx, unused, next) {
if (!ctx.result) return next();

var relationName = ctx.methodString.match(/__([a-z\d]+)$/)[1];

if (
!(options.relations && options.relations.indexOf(relationName) !== -1) &&
!(ctx.args.filter && ctx.args.filter.includeThrough)
) return next();

var relationKey = Model.relations[relationName].keyTo;
var throughKey = Model.relations[relationName].keyThrough;
var relationModel = Model.relations[relationName].modelTo;
var throughModel = Model.relations[relationName].modelThrough;
var idName = relationModel.definition.idName() || 'id';

var newResult = JSON.parse(JSON.stringify(ctx.result));

var query = {where: {}};
query.where[relationKey] = ctx.instance.id;

if (Array.isArray(newResult)) {
query.where[throughKey] = {inq: newResult.map(function(item) { return item[idName]; })};
function controller(ctx, unused, next) {
if (ctx.methodString.indexOf('prototype.__get__') !== -1) {
// the original version
var relationName = ctx.methodString.match(/__([a-z\d]+)$/)[1];
var partialResult = JSON.parse(JSON.stringify(ctx.result));
injectIncludes(ctx, partialResult, relationName).then(function(partialResult) {
ctx.result = partialResult;
next();
});
} else {
query.where[throughKey] = {inq: [newResult[idName]]};
}
// extension
var newResult = JSON.parse(JSON.stringify(ctx.result));

if (
ctx.args.filter &&
ctx.args.filter.includeThrough &&
ctx.args.filter.includeThrough.fields
) {
query.fields = [throughKey, ctx.args.filter.includeThrough.fields];
} else if (options.fields && options.fields[relationName]) {
query.fields = [throughKey, options.fields[relationName]];
var filter = ctx.req && ctx.req.query && ctx.req.query.filter;
if (filter) {
if (typeof filter === 'string') {
filter = JSON.parse(filter);
}
var include = filter.include; // string, [string], object

// only support one level of includes
var relationNames = [];
if (_.isString(include)) {
relationNames.push(include);
} else if (_.isArray(include)) {
for (let elm of include) {
if (typeof elm === 'string') {
relationNames.push(elm);
} else if (typeof elm === 'object') {
for (let prop in elm) {
relationNames.push(prop);
}
}
}
} else if (_.isObject(include)) {
for (let prop in include) {
relationNames.push(prop);
}
}
relationNames = _.uniq(relationNames);

let promises = [];
for (let relationName of relationNames) {
if (modelHasManyThroughRelations.includes(relationName)) {
let partialResult = newResult[relationName];
let promise = injectIncludes(ctx, partialResult, relationName)
.then(function(partialResult) {
return new Promise(function(res, rej) {
newResult[relationName] = partialResult;
res();
});
});
promises.push(promise);
}
}
if (promises.length) {
Promise.all(promises).then(function() {
ctx.result = newResult;
next();
});
} else {
next();
}
} else {
next();
}
}
}

function injectIncludes(ctx, partialResult, relationName) {
return new Promise(function(res, rej) {
var relationSetting, isRelationRegistered;
if (options.relations) {
relationSetting = _.find(options.relations, {name: relationName});
if (relationSetting) {
isRelationRegistered = true;
} else if (options.relations.indexOf(relationName) !== -1) {
isRelationRegistered = true;
relationSetting = {};
relationSetting.name = relationName;
} else {
isRelationRegistered = false;
}
}
if (!isRelationRegistered && !(ctx.args.filter && ctx.args.filter.includeThrough)) {
return res(partialResult);
}

throughModel.find(query, function(err, results) {
if (err) return next();
var relationKey = Model.relations[relationSetting.name].keyTo;
var throughKey = Model.relations[relationSetting.name].keyThrough;
var relationModel = Model.relations[relationSetting.name].modelTo;
var throughModel = Model.relations[relationSetting.name].modelThrough;

var resultsHash = {};
results.forEach(function(result) {
resultsHash[result[throughKey].toString()] = result;
});
if (!relationModel) {
return res(partialResult);
}
var idName = relationModel.definition.idName() || 'id';

if (Array.isArray(newResult)) {
for (var i = 0; i < newResult.length; i++) {
if (resultsHash[newResult[i][idName].toString()]) {
newResult[i][throughModel.definition.name] =
resultsHash[newResult[i][idName].toString()];
}
}
var query = {where: {}};
if (ctx.instance) {
query.where[relationKey] = ctx.instance.id;
} else {
query.where[relationKey] = ctx.args.id;
}

if (Array.isArray(partialResult)) {
query.where[throughKey] = {inq: partialResult.map(function(item) { return item[idName]; })};
} else {
newResult[throughModel.definition.name] = resultsHash[newResult[idName].toString()];
query.where[throughKey] = {inq: [partialResult[idName]]};
}

ctx.result = newResult;
if (
ctx.args.filter &&
ctx.args.filter.includeThrough &&
ctx.args.filter.includeThrough.fields
) {
query.fields = [throughKey, ctx.args.filter.includeThrough.fields];
} else if (options.fields && options.fields[relationSetting.name]) {
query.fields = [throughKey, options.fields[relationSetting.name]];
}

next();
throughModel.find(query, function(err, results) {
if (err) return res(partialResult);
else {
var throughPropertyName = relationSetting.asProperty || throughModel.definition.name;
var resultsHash = {};
results.forEach(function(result) {
resultsHash[result[throughKey].toString()] = result;
});

if (Array.isArray(partialResult)) {
for (var i = 0; i < partialResult.length; i++) {
if (resultsHash[partialResult[i][idName].toString()]) {
partialResult[i][throughPropertyName] =
resultsHash[partialResult[i][idName].toString()];
}
}
} else {
partialResult[throughPropertyName] =
resultsHash[partialResult[idName].toString()];
}
return res(partialResult);
}
});
});
};
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"dependencies": {
"lodash": "^4.17.4"
}
}
Loading

0 comments on commit 16954b1

Please sign in to comment.