Moved "getModelInstance" function to the outside "parseInclude" function and added README and an "include" test about complex include feature
This commit is contained in:
parent
e632f79e2b
commit
05793eb749
50
README.md
50
README.md
@ -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>`.
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
75
src/utils.js
75
src/utils.js
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user