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=city or
// GET /teams?include={"model": "City"} // GET /teams?include={"model": "City"}
// results in a Sequelize query: // results in a Sequelize query:
Team.findAll({include: City}) 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. 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]}) 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 ```js
// returns all team with their related City where City property name equals Healdsburg // returns all team with their related City where City property name equals Healdsburg
// GET /teams?include={"model": "City", "where": {"name": "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'}}}) 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 ## `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>`. 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); 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) => { , async(t) => {
const { server } = t.context; const { server } = t.context;
const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'; 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); t.is(statusCode, STATUS_OK);
}); });
test('nested inlcude filter ' + test('nested include filter ' +
'/city?include[]={"model": "Team", "include": {"model": "Player", "where": {"name": "Pinot"}}}' '/citiy?include[]=' +
, async(t) => { '{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'
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"}}}'
, async(t) => { , async(t) => {
const { instances, server } = t.context; const { instances, server } = t.context;
const { city1, team1, team2 } = instances; const { city1, team1, team2 } = instances;
// eslint-disable-next-line max-len const url = '/city?include[]=' +
const url = '/city?include[]={"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'; '{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}';
const method = 'GET'; const method = 'GET';
const { result, statusCode } = await server.inject({ url, method }); 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(team1.id));
t.truthy(teamIds.includes(team2.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; 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) => { export const parseInclude = async(request) => {
if (typeof request.query.include === 'undefined') return []; if (typeof request.query.include === 'undefined') return [];
@ -29,43 +65,6 @@ export const parseInclude = async(request) => {
const models = getModels(request); const models = getModels(request);
if (models.isBoom) return models; 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 jsonValidation = joi.string().regex(/^\{.*?"model":.*?\}$/);
const includes = include.map(async(b) => { const includes = include.map(async(b) => {
let a = b; let a = b;
@ -77,7 +76,7 @@ export const parseInclude = async(request) => {
// //
} }
return getModelInstance(a); return getModelInstance(models, a);
}).filter(identity); }).filter(identity);
return await Promise.all(includes); return await Promise.all(includes);