Skip to content

Commit c6b050b

Browse files
Add defaultAttributes resolver option
1 parent 4387527 commit c6b050b

File tree

4 files changed

+84
-13
lines changed

4 files changed

+84
-13
lines changed

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# graphql-sequelize
22

33
[![NPM](https://img.shields.io/npm/v/graphql-sequelize.svg)](https://www.npmjs.com/package/graphql-sequelize)
4-
[![Build Status](https://travis-ci.org/mickhansen/graphql-sequelize.svg?branch=master)](https://travis-ci.org/mickhansen/graphql-sequelize)
4+
[![Build Status](https://travis-ci.org/mickhansen/graphql-sequelize.svg?branch=master)](https://travis-ci.org/mickhansen/graphql-sequelize)
55
[![Slack Status](http://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com)
66

77
- [Installation](#installation)
@@ -26,6 +26,9 @@ The graphql-sequelize resolver will by default only select those attributes requ
2626

2727
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})`.
2828

29+
#### Default attributes
30+
When filtering attributes you might need some fields every time, regardless of wether they have been requested in the query. You can specify those fields with the `defaultAttributes` resolver option like `resolver(target, {defaultAttributes: ['default', 'field', 'names']})`. A common use case would be the need to fetch a `userId` for every query and every resource of your domain model for permission checking -- in that case you would write your `resolver` functions like `resolver(target, {defaultAttributes: ['userId']})`.
31+
2932
### Features
3033

3134
- Automatically converts args to where if arg keys matches model attributes
@@ -38,7 +41,7 @@ If you have non-database values that depend on other values you can either solve
3841

3942
[Relay documentation](docs/relay.md)
4043

41-
### Examples
44+
### Examples
4245

4346
```js
4447
import {resolver} from 'graphql-sequelize';
@@ -236,7 +239,7 @@ attributeFields(Model);
236239
### Renaming generated fields
237240

238241
attributeFields accepts a ```map``` option to customize the way the attribute fields are named. The ```map``` option accepts
239-
an object or a function that returns a string.
242+
an object or a function that returns a string.
240243

241244
```js
242245

@@ -336,12 +339,12 @@ fullName: {
336339

337340
### defaultArgs
338341

339-
`defaultArgs(Model)` will return an object containing an arg with a key and type matching your models primary key and
342+
`defaultArgs(Model)` will return an object containing an arg with a key and type matching your models primary key and
340343
the "where" argument for passing complex query operations described [here](http://docs.sequelizejs.com/en/latest/docs/querying/)
341344

342345
```js
343346
var Model = sequelize.define('User', {
344-
347+
345348
});
346349

347350
defaultArgs(Model);
@@ -387,7 +390,7 @@ defaultArgs(Model);
387390
type: GraphQLString
388391
},
389392
where: {
390-
type JSONType
393+
type JSONType
391394
}
392395
}
393396
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"dependencies": {
5555
"babel-runtime": "^6.0.8",
5656
"graphql-relay": "^0.3.6",
57+
"invariant": "2.2.1",
5758
"lodash": "^4.0.0"
5859
}
5960
}

src/resolver.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@ import simplifyAST from './simplifyAST';
44
import generateIncludes from './generateIncludes';
55
import argsToFindOptions from './argsToFindOptions';
66
import { isConnection, handleConnection, nodeAST, nodeType } from './relay';
7+
import invariant from 'invariant';
78

89
function inList(list, attribute) {
910
return ~list.indexOf(attribute);
1011
}
1112

13+
function validateOptions(options) {
14+
invariant(
15+
!options.defaultAttributes || Array.isArray(options.defaultAttributes),
16+
'options.defaultAttributes must be an array of field names.'
17+
);
18+
}
19+
1220
function resolverFactory(target, options) {
1321
var resolver
1422
, targetAttributes
@@ -26,6 +34,8 @@ function resolverFactory(target, options) {
2634
if (options.handleConnection === undefined) options.handleConnection = true;
2735
if (options.filterAttributes === undefined) options.filterAttributes = resolverFactory.filterAttributes;
2836

37+
validateOptions(options);
38+
2939
resolver = function (source, args, info) {
3040
var root = info.rootValue || {}
3141
, ast = info.fieldASTs
@@ -61,6 +71,11 @@ function resolverFactory(target, options) {
6171
findOptions.attributes = Object.keys(fields)
6272
.map(key => fields[key].key || key)
6373
.filter(inList.bind(null, targetAttributes));
74+
75+
if (options.defaultAttributes) {
76+
findOptions.attributes = findOptions.attributes.concat(options.defaultAttributes);
77+
}
78+
6479
} else {
6580
findOptions.attributes = targetAttributes;
6681
}

test/integration/resolver.test.js

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
'use strict';
22

3-
import {expect} from 'chai';
3+
import { expect } from 'chai';
44
import sinon from 'sinon';
55
import Sequelize from 'sequelize';
66

7-
import {sequelize, Promise} from './helper';
7+
import { sequelize, Promise } from './helper';
88
import resolver from '../../src/resolver';
99

1010
import {
@@ -46,8 +46,6 @@ describe('resolver', function () {
4646
return 'lol';
4747
}
4848
}
49-
}, {
50-
timestamps: false
5149
});
5250

5351
Task = sequelize.define('task', {
@@ -309,7 +307,6 @@ describe('resolver', function () {
309307
return graphql(schema, `
310308
{
311309
user(id: ${user.id}) {
312-
id
313310
name
314311
myVirtual
315312
}
@@ -319,7 +316,6 @@ describe('resolver', function () {
319316

320317
expect(result.data).to.deep.equal({
321318
user: {
322-
id: user.id,
323319
name: user.name,
324320
myVirtual: 'lol'
325321
}
@@ -408,7 +404,6 @@ describe('resolver', function () {
408404
it('should resolve an array result with a single model', function () {
409405
var users = this.users;
410406

411-
412407
return graphql(schema, `
413408
{
414409
users {
@@ -1146,4 +1141,61 @@ describe('resolver', function () {
11461141
});
11471142
});
11481143
});
1144+
1145+
describe('defaultAttributes', () => {
1146+
it('should only accept an array as input', () => {
1147+
const f = () => resolver(User, {
1148+
defaultAttributes: 'not_an_array',
1149+
});
1150+
expect(f).to.throw();
1151+
});
1152+
1153+
it('should automatically include fields specified in that array', function () {
1154+
const user = this.userA;
1155+
const sqlSpy = sinon.spy();
1156+
1157+
const makeSchema = (defaultAttributes) => {
1158+
return new GraphQLSchema({
1159+
query: new GraphQLObjectType({
1160+
name: 'RootQueryType',
1161+
fields: {
1162+
user: {
1163+
type: userType,
1164+
args: { id: { type: GraphQLInt } },
1165+
resolve: resolver(User, {
1166+
filterAttributes: true,
1167+
defaultAttributes,
1168+
})
1169+
}
1170+
}
1171+
})
1172+
});
1173+
};
1174+
1175+
const normalSchema = makeSchema();
1176+
1177+
const defaultAttributes = ['createdAt'];
1178+
const schemaWithDefaultAttributes = makeSchema(defaultAttributes);
1179+
1180+
const query = `{ user(id: ${user.id}) { name } } `;
1181+
1182+
return graphql(normalSchema, query, {logging: sqlSpy}).then(function () {
1183+
const [sqlQuery] = sqlSpy.args[0];
1184+
// As we have
1185+
// 1) `filterAttributes` set to true,
1186+
// 2) no `defaultAttributes` defined and
1187+
// 3) `createdAt` hasn't been requested in the graphql query,
1188+
// `createdAt` should not be fetched
1189+
expect(sqlQuery).to.not.contain('createdAt');
1190+
1191+
return graphql(schemaWithDefaultAttributes, query, {logging: sqlSpy});
1192+
}).then(function (result) {
1193+
// With the schema that defined `createdAt` as a default attribute for
1194+
// user, it should be fetched
1195+
const [sqlQuery] = sqlSpy.args[1];
1196+
expect(sqlQuery).to.contain('createdAt');
1197+
expect(result.data.user.createdAt);
1198+
});
1199+
});
1200+
});
11491201
});

0 commit comments

Comments
 (0)