Skip to content

Commit d45e25a

Browse files
committed
attribute filtering option for resolver
1 parent 76a591f commit d45e25a

File tree

4 files changed

+129
-8
lines changed

4 files changed

+129
-8
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ graphql-sequelize assumes you have graphql and sequelize installed.
2020
A helper for resolving graphql queries targeted at Sequelize models or associations.
2121
Please take a look at [the tests](https://github.com/mickhansen/graphql-sequelize/blob/master/test/integration/resolver.test.js) to best get an idea of implementation.
2222

23+
### Attribute filtering
24+
25+
The graphql-sequelize resolver will by default only select those attributes requested from the database.
26+
27+
If you have non-database values that depend on other values you can either solve this by using virtual attributes with dependencies on your model or disable attribute filtering via `resolver.filterAttributes = false` or for specific resolvers with `resolver(target, {filterAttributes: false})`.
28+
2329
### Features
2430

2531
- Automatically converts args to where if arg keys matches model attributes
@@ -28,7 +34,7 @@ Please take a look at [the tests](https://github.com/mickhansen/graphql-sequeliz
2834
- Only loads the attributes defined in the query (automatically adds primary key and foreign keys)
2935
- Prefetching nested resolvers with includes/joins
3036

31-
### Relay
37+
### Relay & Connections
3238

3339
[Relay documentation](docs/relay.md)
3440

src/generateIncludes.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ export default function generateIncludes(simpleAST, type, root, options) {
2222
, includeResolver = type._fields[name].resolve
2323
, nestedResult
2424
, allowedAttributes
25-
, include
26-
, connectionFields = [];
25+
, include;
2726

2827
if (!includeResolver) return;
2928

@@ -64,10 +63,14 @@ export default function generateIncludes(simpleAST, type, root, options) {
6463
includeOptions = argsToFindOptions(args, association.target);
6564
allowedAttributes = Object.keys(association.target.rawAttributes);
6665

67-
includeOptions.attributes = (includeOptions.attributes || [])
66+
67+
if (options.filterAttributes) {
68+
includeOptions.attributes = (includeOptions.attributes || [])
6869
.concat(Object.keys(fieldAST.fields).map(key => fieldAST.fields[key].key || key))
69-
.concat(connectionFields)
7070
.filter(inList.bind(null, allowedAttributes));
71+
} else {
72+
includeOptions.attributes = allowedAttributes;
73+
}
7174

7275
if (includeResolver.$before) {
7376
includeOptions = includeResolver.$before(includeOptions, args, root, {

src/resolver.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function inList(list, attribute) {
99
return ~list.indexOf(attribute);
1010
}
1111

12-
module.exports = function (target, options) {
12+
function resolverFactory(target, options) {
1313
var resolver
1414
, targetAttributes
1515
, isModel = !!target.getTableName
@@ -24,6 +24,7 @@ module.exports = function (target, options) {
2424
if (options.before === undefined) options.before = (options) => options;
2525
if (options.after === undefined) options.after = (result) => result;
2626
if (options.handleConnection === undefined) options.handleConnection = true;
27+
if (options.filterAttributes) options.filterAttributes = resolverFactory.filterAttributes;
2728

2829
resolver = function (source, args, info) {
2930
var root = info.rootValue || {}
@@ -56,9 +57,13 @@ module.exports = function (target, options) {
5657
});
5758
}
5859

59-
findOptions.attributes = Object.keys(fields)
60+
if (options.filterAttributes) {
61+
findOptions.attributes = Object.keys(fields)
6062
.map(key => fields[key].key || key)
6163
.filter(inList.bind(null, targetAttributes));
64+
} else {
65+
findOptions.attributes = targetAttributes;
66+
}
6267

6368
findOptions.attributes.push(model.primaryKeyAttribute);
6469

@@ -118,4 +123,8 @@ module.exports = function (target, options) {
118123
resolver.$options = options;
119124

120125
return resolver;
121-
};
126+
}
127+
128+
resolverFactory.filterAttributes = false;
129+
130+
module.exports = resolverFactory;

test/integration/resolver.test.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,4 +1030,107 @@ describe('resolver', function () {
10301030
});
10311031
});
10321032
});
1033+
1034+
describe('filterAttributes', function () {
1035+
beforeEach(function () {
1036+
this.defaultValue = resolver.filterAttributes;
1037+
this.userType = new GraphQLObjectType({
1038+
name: 'User',
1039+
description: 'A user',
1040+
fields: {
1041+
id: {
1042+
type: new GraphQLNonNull(GraphQLInt),
1043+
},
1044+
name: {
1045+
type: GraphQLString,
1046+
},
1047+
fullName: {
1048+
type: GraphQLString,
1049+
resolve: (user) => user.get('name')
1050+
}
1051+
}
1052+
});
1053+
});
1054+
1055+
afterEach(function () {
1056+
resolver.filterAttributes = this.defaultValue;
1057+
});
1058+
1059+
it('should be able to disable via a global option', async function () {
1060+
resolver.filterAttributes = false;
1061+
1062+
var user = this.userB
1063+
, schema;
1064+
1065+
schema = new GraphQLSchema({
1066+
query: new GraphQLObjectType({
1067+
name: 'RootQueryType',
1068+
fields: {
1069+
user: {
1070+
type: this.userType,
1071+
args: {
1072+
id: {
1073+
type: GraphQLInt
1074+
}
1075+
},
1076+
resolve: resolver(User, {
1077+
include: false
1078+
})
1079+
}
1080+
}
1081+
})
1082+
});
1083+
1084+
return graphql(schema, `
1085+
{
1086+
user(id: ${user.get('id')}) {
1087+
fullName
1088+
}
1089+
}
1090+
`).then(function (result) {
1091+
if (result.errors) throw new Error(result.errors[0].stack);
1092+
1093+
expect(result.data.user.fullName).to.equal(user.get('name'));
1094+
});
1095+
});
1096+
1097+
it('should be able to disable via a resolver option', async function () {
1098+
resolver.filterAttributes = true;
1099+
1100+
var user = this.userB
1101+
, schema;
1102+
1103+
schema = new GraphQLSchema({
1104+
query: new GraphQLObjectType({
1105+
name: 'RootQueryType',
1106+
fields: {
1107+
user: {
1108+
type: this.userType,
1109+
args: {
1110+
id: {
1111+
type: GraphQLInt
1112+
}
1113+
},
1114+
resolve: resolver(User, {
1115+
include: false,
1116+
filterAttributes: false
1117+
})
1118+
}
1119+
}
1120+
})
1121+
});
1122+
1123+
return graphql(schema, `
1124+
{
1125+
user(id: ${user.get('id')}) {
1126+
fullName
1127+
}
1128+
}
1129+
`).then(function (result) {
1130+
if (result.errors) throw new Error(result.errors[0].stack);
1131+
1132+
expect(result.data.user.fullName).to.equal(user.get('name'));
1133+
});
1134+
});
1135+
});
10331136
});

0 commit comments

Comments
 (0)