Skip to content

Commit 7537528

Browse files
committed
Add Relationship fields and resolvers
1 parent 81ec19c commit 7537528

File tree

8 files changed

+124
-22
lines changed

8 files changed

+124
-22
lines changed

src/introspection/getFieldsFromEntities.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import getValuesFromEntities from './getValuesFromEntities';
1919
* "user_id": 456,
2020
* },
2121
* ];
22-
* const types = getFieldsFromData(entities);
22+
* const types = getFieldsFromEntities(entities);
2323
* // {
2424
* // id: { type: graphql.GraphQLString },
2525
* // title: { type: graphql.GraphQLString },

src/introspection/getSchemaFromData.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import {
77
GraphQLObjectType,
88
GraphQLSchema,
99
GraphQLString,
10+
parse,
11+
extendSchema,
1012
} from 'graphql';
1113
import { pluralize, camelize } from 'inflection';
1214

1315
import getTypesFromData from './getTypesFromData';
16+
import { isRelationshipField } from '../relationships';
17+
import { getRelatedType } from '../nameConverter';
1418

1519
/**
1620
* Get a GraphQL schema from data
@@ -153,5 +157,25 @@ export default data => {
153157
}, {}),
154158
});
155159

156-
return new GraphQLSchema({ query: queryType, mutation: mutationType });
160+
let schema = new GraphQLSchema({
161+
query: queryType,
162+
mutation: mutationType,
163+
});
164+
165+
// extend schema to add relationship fields
166+
Object.values(typesByName).map(type => {
167+
Object.keys(type.getFields())
168+
.filter(isRelationshipField)
169+
.map(fieldName => {
170+
const relatedType = getRelatedType(fieldName);
171+
schema = extendSchema(
172+
schema,
173+
parse(
174+
`extend type ${type} { ${relatedType}: ${relatedType}} `,
175+
),
176+
);
177+
});
178+
});
179+
180+
return schema;
157181
};

src/introspection/getSchemaFromData.spec.js

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
GraphQLBoolean,
33
GraphQLID,
44
GraphQLInt,
5-
GraphQLList,
65
GraphQLNonNull,
76
GraphQLObjectType,
87
GraphQLString,
@@ -36,31 +35,34 @@ const data = {
3635
],
3736
};
3837

38+
const UserType = new GraphQLObjectType({
39+
name: 'User',
40+
fields: {
41+
id: { type: new GraphQLNonNull(GraphQLID) },
42+
name: { type: new GraphQLNonNull(GraphQLString) },
43+
},
44+
});
45+
3946
const PostType = new GraphQLObjectType({
4047
name: 'Post',
4148
fields: {
4249
id: { type: new GraphQLNonNull(GraphQLID) },
4350
title: { type: new GraphQLNonNull(GraphQLString) },
4451
views: { type: new GraphQLNonNull(GraphQLInt) },
4552
user_id: { type: new GraphQLNonNull(GraphQLID) },
46-
},
47-
});
48-
const UserType = new GraphQLObjectType({
49-
name: 'User',
50-
fields: {
51-
id: { type: new GraphQLNonNull(GraphQLID) },
52-
name: { type: new GraphQLNonNull(GraphQLString) },
53+
User: { type: UserType },
5354
},
5455
});
5556

57+
/*
5658
const ListMetadataType = new GraphQLObjectType({
5759
name: 'ListMetadata',
5860
fields: {
5961
count: { type: GraphQLInt },
6062
},
6163
});
6264
63-
const QueryType = new GraphQLObjectType({ // eslint-disable-line
65+
const QueryType = new GraphQLObjectType({
6466
name: 'Query',
6567
fields: {
6668
getPost: {
@@ -97,13 +99,18 @@ const QueryType = new GraphQLObjectType({ // eslint-disable-line
9799
},
98100
},
99101
});
102+
*/
100103

101104
test('creates one type per data type', () => {
102105
const typeMap = getSchemaFromData(data).getTypeMap();
103106
expect(typeMap['Post'].name).toEqual(PostType.name);
104-
expect(typeMap['Post'].fields).toEqual(PostType.fields);
107+
expect(Object.keys(typeMap['Post'].getFields())).toEqual(
108+
Object.keys(PostType.getFields()),
109+
);
105110
expect(typeMap['User'].name).toEqual(UserType.name);
106-
expect(typeMap['User'].fields).toEqual(UserType.fields);
111+
expect(Object.keys(typeMap['User'].getFields())).toEqual(
112+
Object.keys(UserType.getFields()),
113+
);
107114
});
108115

109116
test('creates three query fields per data type', () => {
@@ -117,7 +124,7 @@ test('creates three query fields per data type', () => {
117124
type: new GraphQLNonNull(GraphQLID),
118125
},
119126
]);
120-
expect(queries['allPosts'].type).toMatchObject(new GraphQLList(PostType));
127+
expect(queries['allPosts'].type.toString()).toEqual('[Post]');
121128
expect(queries['allPosts'].args).toEqual([
122129
{
123130
defaultValue: undefined,
@@ -150,7 +157,7 @@ test('creates three query fields per data type', () => {
150157
type: GraphQLString,
151158
},
152159
]);
153-
expect(queries['_allPostsMeta'].type).toMatchObject(ListMetadataType);
160+
expect(queries['_allPostsMeta'].type.toString()).toEqual('ListMetadata');
154161

155162
expect(queries['User'].type.name).toEqual(UserType.name);
156163
expect(queries['User'].args).toEqual([
@@ -161,7 +168,7 @@ test('creates three query fields per data type', () => {
161168
type: new GraphQLNonNull(GraphQLID),
162169
},
163170
]);
164-
expect(queries['allUsers'].type).toMatchObject(new GraphQLList(UserType));
171+
expect(queries['allUsers'].type.toString()).toEqual('[User]');
165172
expect(queries['allUsers'].args).toEqual([
166173
{
167174
defaultValue: undefined,
@@ -194,7 +201,7 @@ test('creates three query fields per data type', () => {
194201
type: GraphQLString,
195202
},
196203
]);
197-
expect(queries['_allPostsMeta'].type).toMatchObject(ListMetadataType);
204+
expect(queries['_allPostsMeta'].type.toString()).toEqual('ListMetadata');
198205
});
199206

200207
test('creates three mutation fields per data type', () => {

src/introspection/getTypesFromData.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GraphQLObjectType } from 'graphql';
22
import { singularize, camelize } from 'inflection';
33

44
import getFieldsFromEntities from './getFieldsFromEntities';
5+
import { getTypeFromKey } from '../nameConverter';
56

67
/**
78
* Get a list of GraphQLObjectType from data
@@ -61,6 +62,5 @@ export default data =>
6162
}))
6263
.map(typeObject => new GraphQLObjectType(typeObject));
6364

64-
export const getTypeNameFromKey = key => camelize(singularize(key));
6565
export const getTypeNamesFromData = data =>
66-
Object.keys(data).map(getTypeNameFromKey);
66+
Object.keys(data).map(getTypeFromKey);

src/nameConverter.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { camelize, pluralize, singularize } from 'inflection';
2+
3+
/**
4+
* A bit of vocabulary
5+
*
6+
* Consider this data:
7+
* {
8+
* posts: [
9+
* { id: 1, title: 'foo', user_id: 123 }
10+
* ],
11+
* users: [
12+
* { id: 123, name: 'John Doe' }
13+
* ]
14+
* }
15+
*
16+
* We'll use the following names:
17+
* - key: the keys in the data map, e.g. 'posts', 'users'
18+
* - type: for a key, the related type in the graphQL schema, e.g. 'posts' => 'Post', 'users' => 'User'
19+
* - field: the keys in a record, e.g. 'id', 'foo', user_id'
20+
* - relationship field: a key ending in '_id', e.g. 'user_id'
21+
* - related key: for a relationship field, the related key, e.g. 'user_id' => 'users'
22+
*/
23+
24+
/**
25+
*
26+
* @param {String} fieldName 'users'
27+
* @return {String} 'User'
28+
*/
29+
export const getTypeFromKey = key => camelize(singularize(key));
30+
31+
/**
32+
*
33+
* @param {String} fieldName 'user_id'
34+
* @return {String} 'users'
35+
*/
36+
export const getRelatedKey = fieldName =>
37+
pluralize(fieldName.substr(0, fieldName.length - 3));
38+
39+
/**
40+
*
41+
* @param {String} fieldName 'user_id'
42+
* @return {String} 'User'
43+
*/
44+
export const getRelatedType = fieldName =>
45+
getTypeFromKey(fieldName.substr(0, fieldName.length - 3));

src/relationships.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const isRelationshipField = fieldName => fieldName.endsWith('_id');

src/resolver/Entity/index.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import getFieldsFromEntities from '../../introspection/getFieldsFromEntities';
2+
import { getRelatedKey, getRelatedType } from '../../nameConverter';
3+
import { isRelationshipField } from '../../relationships';
4+
5+
export default (key, data) =>
6+
Object.keys(getFieldsFromEntities(data[key]))
7+
.filter(isRelationshipField)
8+
.reduce(
9+
(resolvers, fieldName) => ({
10+
...resolvers,
11+
[getRelatedType(fieldName)]: entity =>
12+
data[getRelatedKey(fieldName)].find(
13+
relatedRecord => relatedRecord.id == entity[fieldName],
14+
),
15+
}),
16+
{},
17+
);

src/resolver/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import single from './Query/single';
66
import create from './Mutation/create';
77
import update from './Mutation/update';
88
import remove from './Mutation/remove';
9-
import { getTypeNameFromKey } from '../introspection/getTypesFromData';
9+
import entityResolver from './Entity';
10+
import { getTypeFromKey } from '../nameConverter';
1011

1112
const getQueryResolvers = (entityName, data) => ({
1213
[`all${pluralize(entityName)}`]: all(data),
@@ -25,14 +26,21 @@ export default data => {
2526
Query: Object.keys(data).reduce(
2627
(resolvers, key) => ({
2728
...resolvers,
28-
...getQueryResolvers(getTypeNameFromKey(key), data[key]),
29+
...getQueryResolvers(getTypeFromKey(key), data[key]),
2930
}),
3031
{},
3132
),
3233
Mutation: Object.keys(data).reduce(
3334
(resolvers, key) => ({
3435
...resolvers,
35-
...getMutationResolvers(getTypeNameFromKey(key), data[key]),
36+
...getMutationResolvers(getTypeFromKey(key), data[key]),
37+
}),
38+
{},
39+
),
40+
...Object.keys(data).reduce(
41+
(resolvers, key) => ({
42+
...resolvers,
43+
[getTypeFromKey(key)]: entityResolver(key, data),
3644
}),
3745
{},
3846
),

0 commit comments

Comments
 (0)