Moved "getModelInstance" function to the outside "parseInclude" function and added README and an "include" test about complex include feature

This commit is contained in:
Muhammad Labib Ramadhan 2016-11-11 09:03:01 +07:00
parent e632f79e2b
commit 05793eb749
3 changed files with 111 additions and 61 deletions

View File

@ -112,9 +112,14 @@ Getting related models is easy, just use a query parameter `include`.
// GET /teams?include=city or
// GET /teams?include={"model": "City"}
// results in a Sequelize query:
Team.findAll({include: City})
or if association defined with an alias
// GET /players?include={"model": "Master", "as": "Couch"}
// results in a Sequelize query:
Players.findAll({include: Master, as: 'Couch'})
```
If you want to get multiple related models, just pass multiple `include` parameters.
@ -135,7 +140,7 @@ For models that have a many-to-many relationship, you can also pass the plural v
Team.findAll({include: [Player]})
```
Filtering by related models property, you can pass **where** paremeter inside each **include** item(s) object.
Filtering by related models property, you can pass **where** paremeter inside each **include** items object.
```js
// returns all team with their related City where City property name equals Healdsburg
// GET /teams?include={"model": "City", "where": {"name": "Healdsburg"}}
@ -144,6 +149,47 @@ Filtering by related models property, you can pass **where** paremeter inside ea
Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}})
```
More complex example with nested include, association alias and association filtering.
```js
// returns all team with its players along with its couch of each player
// GET /cities?include[]={
// "model": "Team",
// "include": {
// "model": "Player",
// "where": {
// "name": "Pinot"
// },
// "include": {
// "model": "Master",
// "as": "Coach",
// "where": {
// "name": "Shifu"
// }
// }
// }
// }
// results in a Sequelize query:
City.findAll({
include: {
model: Team,
include: {
model: Player,
where: {
name: Pinot
},
include: {
model: Master,
as: 'Coach',
where: {
name: 'Shifu'
}
}
}
}
})
```
## `limit` and `offset` queries
Restricting list (`GET`) and scope queries to a restricted count can be done by passing `limit=<number>` and/or `offset=<number>`.

View File

@ -112,7 +112,7 @@ test('multiple includes /team?include[]=players&include[]={"model": "City"}', as
t.is(result.City.id, city1.id);
});
test('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'
test('include filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'
, async(t) => {
const { server } = t.context;
const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}';
@ -122,29 +122,14 @@ test('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Heald
t.is(statusCode, STATUS_OK);
});
test('nested inlcude filter ' +
'/city?include[]={"model": "Team", "include": {"model": "Player", "where": {"name": "Pinot"}}}'
, async(t) => {
const { instances, server } = t.context;
const { city1, team1, player2 } = instances;
// eslint-disable-next-line max-len
const url = '/city?include[]={"model": "Team", "include": {"model": "Player", "where": {"name": "Pinot"}}}';
const method = 'GET';
const { result, statusCode } = await server.inject({ url, method });
t.is(statusCode, STATUS_OK);
t.is(result.id, city1.id);
t.is(result.Teams[0].id, team1.id);
t.is(result.Teams[0].Players[0].id, player2.id);
});
test('nested inlcude filter ' +
'/city?include[]={"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'
test('nested include filter ' +
'/citiy?include[]=' +
'{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'
, async(t) => {
const { instances, server } = t.context;
const { city1, team1, team2 } = instances;
// eslint-disable-next-line max-len
const url = '/city?include[]={"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}';
const url = '/city?include[]=' +
'{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}';
const method = 'GET';
const { result, statusCode } = await server.inject({ url, method });
@ -155,3 +140,23 @@ test('nested inlcude filter ' +
t.truthy(teamIds.includes(team1.id));
t.truthy(teamIds.includes(team2.id));
});
test('complex include ' +
'/cities?include[]={"model":"Team", ' +
'"include":{ "model":"Player", "where":{"name": "Pinot"}, ' +
'"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}'
, async(t) => {
const { instances, server } = t.context;
const { city1, master1, player2, team1 } = instances;
const method = 'GET';
const url = '/cities?include[]={"model":"Team", ' +
'"include":{ "model":"Player", "where":{"name": "Pinot"}, ' +
'"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}';
const { result, statusCode } = await server.inject({ url, method });
t.is(statusCode, STATUS_OK);
t.is(result[0].id, city1.id);
t.is(result[0].Teams[0].id, team1.id);
t.is(result[0].Teams[0].Players[0].id, player2.id);
t.is(result[0].Teams[0].Players[0].Coach.id, master1.id);
});

View File

@ -18,6 +18,42 @@ const getModels = (request) => {
return models;
};
const getModelInstance = (models, includeItem) => {
return new Promise(async(resolve) => {
if (includeItem) {
if (typeof includeItem !== 'object') {
const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
const { _singular, _plural } = models[modelName];
return _singular === includeItem || _plural === includeItem;
});
if (singluarOrPluralMatch) {
return resolve(models[singluarOrPluralMatch]);
}
}
if (typeof includeItem === 'string' && models.hasOwnProperty(includeItem)) {
return resolve(models[includeItem]);
} else if (typeof includeItem === 'object') {
if (
typeof includeItem.model === 'string' &&
includeItem.model.length &&
models.hasOwnProperty(includeItem.model)
) {
includeItem.model = models[includeItem.model];
}
if (includeItem.hasOwnProperty('include')) {
includeItem.include = await getModelInstance(models, includeItem.include);
return resolve(includeItem);
} else {
return resolve(includeItem);
}
}
}
return resolve(includeItem);
});
};
export const parseInclude = async(request) => {
if (typeof request.query.include === 'undefined') return [];
@ -29,43 +65,6 @@ export const parseInclude = async(request) => {
const models = getModels(request);
if (models.isBoom) return models;
const getModelInstance = includeItem => {
return new Promise(async(resolve) => {
if (includeItem) {
if (typeof includeItem !== 'object') {
const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
const { _singular, _plural } = models[modelName];
return _singular === includeItem || _plural === includeItem;
});
if (singluarOrPluralMatch) {
return resolve(models[singluarOrPluralMatch]);
}
}
if (typeof includeItem === 'string' && models.hasOwnProperty(includeItem)) {
return resolve(models[includeItem]);
} else if (typeof includeItem === 'object') {
if (
typeof includeItem.model === 'string' &&
includeItem.model.length &&
models.hasOwnProperty(includeItem.model)
) {
includeItem.model = models[includeItem.model];
}
if (includeItem.hasOwnProperty('include')) {
includeItem.include = await getModelInstance(includeItem.include);
return resolve(includeItem);
} else {
return resolve(includeItem);
}
}
} else {
return resolve(includeItem);
}
});
};
const jsonValidation = joi.string().regex(/^\{.*?"model":.*?\}$/);
const includes = include.map(async(b) => {
let a = b;
@ -77,7 +76,7 @@ export const parseInclude = async(request) => {
//
}
return getModelInstance(a);
return getModelInstance(models, a);
}).filter(identity);
return await Promise.all(includes);