Skip to content

Commit

Permalink
feat(schema): add read support types for objects within a list
Browse files Browse the repository at this point in the history
For example take this schema:

```javascript
const PostSchema = new mongoose.Schema({
  title: String,
  authors: [{
    username: String,
    email: String
  }]
})
```

Before this commit `authors` would be mapped to `GraphQLGeneric`. This commit
generates the appropriate nested types.

Incidentally, this commit also fixes resolving refs within nested objects
and lists.

For instance:

```javascript
const UserSchema = new mongoose.Schema({
  name: {
    type: String
  },
  sub: {
    subsub: {
      sister: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
      }
    }
  }
});
```

Previously `user.sub.subsub.sister` would have resolved only to an `ID`. This
commit causes this case to resolve properly to a `User`.

This change only applies to queries. Mutations of objects nested in a list
are a TODO.
  • Loading branch information
jashmenn authored and Andras Toth committed Dec 16, 2015
1 parent 6aa5820 commit c473304
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 27 deletions.
10 changes: 9 additions & 1 deletion fixture/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ const UserSchema = new mongoose.Schema({
subsub: {
bar: Number
}
}
},
subArray: [{
foo: String,
nums: [Number],
brother: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}]
});

const User = mongoose.model('User', UserSchema);
Expand Down
13 changes: 12 additions & 1 deletion src/model/model.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {reduce, reduceRight, merge} from 'lodash';
import mongoose from 'mongoose';

/**
* @method getField
Expand Down Expand Up @@ -62,7 +63,17 @@ function extractPath(schemaPath) {
return reduceRight(subs, (field, sub, key) => {
const obj = {};

if (key === (subs.length - 1)) {
if (schemaPath instanceof mongoose.Schema.Types.DocumentArray) {
const subSchemaPaths = schemaPath.schema.paths;
const fields = extractPaths(subSchemaPaths, {name: sub}); // eslint-disable-line no-use-before-define
obj[sub] = {
name: sub,
nonNull: false,
type: 'Array',
subtype: 'Object',
fields
};
} else if (key === (subs.length - 1)) {
obj[sub] = getField(schemaPath);
} else {
obj[sub] = {
Expand Down
4 changes: 3 additions & 1 deletion src/schema/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ function getMutationField(graffitiModel, type, viewer, hooks = {}) {
}
}

if (!(field.type instanceof GraphQLObjectType) && field.name !== 'id' && !field.name.startsWith('_')) {
if (field.type instanceof GraphQLList && field.type.ofType instanceof GraphQLObjectType) {
// TODO support objects nested in lists
} else if (!(field.type instanceof GraphQLObjectType) && field.name !== 'id' && !field.name.startsWith('_')) {
inputFields[field.name] = field;
}

Expand Down
65 changes: 46 additions & 19 deletions src/type/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,16 @@ const resolveReference = {};
* @param {Boolean} root
* @return {GraphQLObjectType}
*/
export default function getType(graffitiModels, {name, description, fields}, root = true) {
export default function getType(graffitiModels, {name, description, fields}, path = [], rootType = null) {
const root = path.length === 0;
const graphQLType = {name, description};
rootType = rootType || graphQLType;

// These references has to be resolved when all type definitions are avaiable
// These references have to be resolved when all type definitions are avaiable
resolveReference[graphQLType.name] = resolveReference[graphQLType.name] || {};
const graphQLTypeFields = reduce(fields, (graphQLFields, {name, description, type, subtype, reference, nonNull, hidden, hooks, fields: subfields}, key) => {
name = name || key;
const newPath = [...path, name];

// Don't add hidden fields to the GraphQLObjectType
if (hidden || name.startsWith('__')) {
Expand All @@ -181,27 +184,32 @@ export default function getType(graffitiModels, {name, description, fields}, roo
const graphQLField = {name, description};

if (type === 'Array') {
graphQLField.type = new GraphQLList(stringToGraphQLType(subtype));
if (reference) {
resolveReference[graphQLType.name][name] = {
name,
type: reference,
args: connectionArgs,
resolve: addHooks((rootValue, args, info) => {
args.id = rootValue[name].map((i) => i.toString());
return connectionFromModel(graffitiModels[reference], args, info);
}, hooks)
};
if (subtype === 'Object') {
const fields = subfields;
graphQLField.type = new GraphQLList(getType(graffitiModels, {name, description, fields}, newPath, rootType));
} else {
graphQLField.type = new GraphQLList(stringToGraphQLType(subtype));
if (reference) {
resolveReference[rootType.name][name] = {
name,
type: reference,
args: connectionArgs,
resolve: addHooks((rootValue, args, info) => {
args.id = rootValue[name].map((i) => i.toString());
return connectionFromModel(graffitiModels[reference], args, info);
}, hooks)
};
}
}
} else if (type === 'Object') {
const fields = subfields;
graphQLField.type = getType(graffitiModels, {name, description, fields}, false);
graphQLField.type = getType(graffitiModels, {name, description, fields}, newPath, rootType);
} else {
graphQLField.type = stringToGraphQLType(type);
}

if (reference && (graphQLField.type === GraphQLID || graphQLField.type === new GraphQLNonNull(GraphQLID))) {
resolveReference[graphQLType.name][name] = {
resolveReference[rootType.name][newPath.join('.')] = {
name,
type: reference,
resolve: addHooks((rootValue, args, info) => {
Expand Down Expand Up @@ -270,10 +278,29 @@ function getTypes(graffitiModels) {
field.type = types[field.type];
}

return {
...typeFields,
[fieldName]: field
};
// deeply find the path of the field we want to resolve the reference of
const path = fieldName.split('.');
const newTypeFields = {...typeFields};
let parent = newTypeFields;
let segment;

while (path.length > 0) {
segment = path.shift();

if (parent[segment]) {
if (parent[segment].type instanceof GraphQLObjectType) {
parent = parent[segment].type.getFields();
} else if (parent[segment].type instanceof GraphQLList &&
parent[segment].type.ofType instanceof GraphQLObjectType) {
parent = getTypeFields(parent[segment].type.ofType);
}
}
}

if (path.length === 0) {
parent[segment] = field;
}
return newTypeFields;
}, getTypeFields(type));

// Add new fields
Expand Down
57 changes: 52 additions & 5 deletions src/type/type.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,33 @@ describe('type', () => {
fields: {
bar: {
type: 'Number'
},
sister: {
type: 'ObjectID',
reference: 'User',
description: 'The user\'s sister'
}
}
}
}
},
subArray: {
type: 'Array',
subtype: 'Object',
fields: {
foo: {
type: 'String'
},
nums: {
type: 'Array',
subtype: 'Number'
},
brother: {
type: 'ObjectID',
reference: 'User',
description: 'The user\'s brother'
}
}
}
}
};
Expand All @@ -96,7 +119,7 @@ describe('type', () => {

it('should specify the fields', () => {
const result = getType([], user);
let fields = result._typeConfig.fields();
const fields = result._typeConfig.fields();
expect(fields).to.containSubset({
name: {
name: 'name',
Expand Down Expand Up @@ -141,8 +164,8 @@ describe('type', () => {
});

// sub
fields = fields.sub.type._typeConfig.fields();
expect(fields).to.containSubset({
const subFields = fields.sub.type._typeConfig.fields();
expect(subFields).to.containSubset({
foo: {
name: 'foo',
type: GraphQLString
Expand All @@ -157,11 +180,33 @@ describe('type', () => {
});

// subsub
fields = fields.subsub.type._typeConfig.fields();
expect(fields).to.containSubset({
const subsubFields = subFields.subsub.type._typeConfig.fields();
expect(subsubFields).to.containSubset({
bar: {
name: 'bar',
type: GraphQLFloat
},
sister: {
name: 'sister',
type: GraphQLID,
description: 'The user\'s sister'
}
});

const subArrayFields = fields.subArray.type.ofType._typeConfig.fields();
expect(subArrayFields).to.containSubset({
foo: {
name: 'foo',
type: GraphQLString
},
nums: {
name: 'nums',
type: new GraphQLList(GraphQLFloat)
},
brother: {
name: 'brother',
type: GraphQLID,
description: 'The user\'s brother'
}
});
});
Expand All @@ -180,6 +225,8 @@ describe('type', () => {
const fields = userType._typeConfig.fields();

expect(fields.mother.type).to.be.equal(userType);
expect(fields.sub.type._fields.subsub.type._fields.sister.type).to.be.equal(userType);
expect(fields.subArray.type.ofType._typeConfig.fields().brother.type).to.be.equal(userType);

// connection type
const nodeField = fields.friends.type._typeConfig.fields().edges.type.ofType._typeConfig.fields().node;
Expand Down

0 comments on commit c473304

Please sign in to comment.