{"action":"created","issue":{"url":"https://api.github.com/repos/graphql/graphql-js/issues/733","repository_url":"https://api.github.com/repos/graphql/graphql-js","labels_url":"https://api.github.com/repos/graphql/graphql-js/issues/733/labels{/name}","comments_url":"https://api.github.com/repos/graphql/graphql-js/issues/733/comments","events_url":"https://api.github.com/repos/graphql/graphql-js/issues/733/events","html_url":"https://github.com/graphql/graphql-js/issues/733","id":210927769,"number":733,"title":"Custom Default Resolver","user":{"login":"southpolesteve","id":471400,"avatar_url":"https://avatars1.githubusercontent.com/u/471400?v=4","gravatar_id":"","url":"https://api.github.com/users/southpolesteve","html_url":"https://github.com/southpolesteve","followers_url":"https://api.github.com/users/southpolesteve/followers","following_url":"https://api.github.com/users/southpolesteve/following{/other_user}","gists_url":"https://api.github.com/users/southpolesteve/gists{/gist_id}","starred_url":"https://api.github.com/users/southpolesteve/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/southpolesteve/subscriptions","organizations_url":"https://api.github.com/users/southpolesteve/orgs","repos_url":"https://api.github.com/users/southpolesteve/repos","events_url":"https://api.github.com/users/southpolesteve/events{/privacy}","received_events_url":"https://api.github.com/users/southpolesteve/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2017-02-28T22:57:12Z","updated_at":"2018-02-12T02:49:27Z","closed_at":"2017-05-18T15:43:58Z","author_association":"NONE","body":"We are finding ourselves writing a lot of field resolvers that have very similar logic. There are a number of ways to deal with this, but I would like to propose adding a `defaultResolve` option that will override the built in default resolver for any fields of an object.\r\n\r\n### Before\r\n```\r\nconst Post = new GraphQLObjectType({\r\n  name: 'Post',\r\n  fields: {\r\n    title: {\r\n      type: GraphQLString,\r\n      resolve: (root, args, ctx) => ctx.db.load(root, 'title')\r\n    },\r\n    author: {\r\n      type: GraphQLString,\r\n      resolve: (root, args, ctx) => ctx.db.load(root, 'author')\r\n    }\r\n  }\r\n})\r\n```\r\n\r\n### After\r\n```\r\nconst Post = new GraphQLObjectType({\r\n  name: 'Post',\r\n  defaultResolve: (root, args, ctx, info) => ctx.db.load(root, info.fieldName)\r\n  fields: {\r\n    title: { type: GraphQLString },\r\n    author: { type: GraphQLString }\r\n  }\r\n})\r\n```\r\n\r\nYou can imagine how much this dries up code when you have a lot of fields. I am happy to supply a PR to implement this if the maintainers feel it would be a valuable addition."},"comment":{"url":"https://api.github.com/repos/graphql/graphql-js/issues/comments/364817605","html_url":"https://github.com/graphql/graphql-js/issues/733#issuecomment-364817605","issue_url":"https://api.github.com/repos/graphql/graphql-js/issues/733","id":364817605,"user":{"login":"timhuff","id":609217,"avatar_url":"https://avatars3.githubusercontent.com/u/609217?v=4","gravatar_id":"","url":"https://api.github.com/users/timhuff","html_url":"https://github.com/timhuff","followers_url":"https://api.github.com/users/timhuff/followers","following_url":"https://api.github.com/users/timhuff/following{/other_user}","gists_url":"https://api.github.com/users/timhuff/gists{/gist_id}","starred_url":"https://api.github.com/users/timhuff/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timhuff/subscriptions","organizations_url":"https://api.github.com/users/timhuff/orgs","repos_url":"https://api.github.com/users/timhuff/repos","events_url":"https://api.github.com/users/timhuff/events{/privacy}","received_events_url":"https://api.github.com/users/timhuff/received_events","type":"User","site_admin":false},"created_at":"2018-02-12T02:49:27Z","updated_at":"2018-02-12T02:49:27Z","author_association":"NONE","body":"I was helped by this page so I figured I'd share alike. Inspired by @southpolesteve, I wrote the follow. It's a little different. \r\n\r\nOne thing that differs is that it doesn't assume a resolve method. It instead creates two Schema templates - one for a normal schema and one for a list schema. If you decide to use these, then it's assumed that you've defined the appropriate methods on that subclass (and you'll get an error if you haven't, rather than the thing just silently failing).\r\n\r\nAnother difference is that I decided to take the opportunity to resolve the circular dependency problem. `graphql` permits you to put `fields` as a function that's deferred a tick but that really only solves the problem if you have all these things defined in the same file. I feel that the empty parameter list when calling that function is a real missed opportunity, so I pass a hash of the created types (accumulated in the `types` object) into the `fields` function when I call it. This permits the subclass to extract them for usage in its fields definition (see `SessionType` below).\r\n\r\nI later decided to replace `types` with a `Proxy` (see bottom). In the event that a type is removed and you forget that some other subclass is dependent upon it, it will throw an error informing you of this fact. I left this out of the code below in an effort to keep it simple.\r\n\r\n#### Footnote: \r\nI commented out a line that makes reference to `gs`. This is the package `graphql-sequelize`, which offers utils for defining graphql schemas in terms of sequelize models.\r\n\r\n## Base Class\r\n```javascript\r\nimport { GraphQLObjectType, GraphQLList } from 'graphql'\r\n\r\nlet types = {}\r\nexport default class ModelType extends GraphQLObjectType {\r\n  constructor({ fields, ...config }) {\r\n    super({\r\n      ...config,\r\n      fields: typeof fields === 'function' ? () => fields(types) : fields,\r\n    })\r\n\r\n    types[this.constructor.name] = this\r\n\r\n    this.Schema = {\r\n      type: this,\r\n      resolve: this.get,\r\n    }\r\n    this.ListSchema = {\r\n      type: new GraphQLList(this),\r\n      resolve: this.query,\r\n    }\r\n\r\n    this.get = this.get.bind(this)\r\n    this.query = this.query.bind(this)\r\n  }\r\n\r\n  get() {\r\n    throw new Error(\r\n      `Someone is trying to use ${this.constructor.name}.Schema but no \"get\" function is defined for that type.`\r\n    )\r\n  }\r\n  query() {\r\n    throw new Error(\r\n      `Someone is trying to use ${this.constructor.name}.ListSchema but no \"query\" function is defined for that type.`\r\n    )\r\n  }\r\n}\r\n```\r\n\r\n## Extended Class:\r\n\r\n```javascript\r\n\r\nclass SessionType extends ModelType {\r\n  constructor() {\r\n    super({\r\n      name: 'Session',\r\n      description: \"A user's web session\",\r\n      fields: ({ UserType }) => ({\r\n        // ...gs.attributeFields(DB.Session, { exclude: ['token'] }),\r\n        user: {\r\n          ...UserType.Schema,\r\n          resolve: this.getUser,\r\n        },\r\n      }),\r\n    })\r\n  }\r\n\r\n  async get(parent, args, context) {\r\n    // ...\r\n  }\r\n\r\n  async getUser(parent, args, context) {\r\n    // ...\r\n  }\r\n}\r\n```\r\n\r\n## Schema definition\r\n```javascript\r\nimport { SessionType } from 'api/types'\r\n\r\nexport default new GraphQLSchema({\r\n  query: new GraphQLObjectType({\r\n    name: 'Queries',\r\n    description: 'Root query object',\r\n    fields() {\r\n      return {\r\n        session: SessionType.Schema,\r\n      }\r\n    },\r\n  }),\r\n  mutation: new GraphQLObjectType({\r\n    name: 'Mutations',\r\n    description: 'Functions to create or modify stuff',\r\n    fields() {\r\n      return {\r\n        create_session: {\r\n          ...SessionType.Schema,\r\n          args: {\r\n            email: { type: new GraphQLNonNull(GraphQLString) },\r\n            password: { type: new GraphQLNonNull(GraphQLString) },\r\n          },\r\n          resolve: SessionType.create,\r\n        },\r\n      }\r\n    },\r\n  }),\r\n})\r\n```\r\n\r\n## `types` Proxy\r\n```javascript\r\n\r\nconst types = new Proxy(\r\n  {},\r\n  {\r\n    get: (target, name) => {\r\n      if (!target[name])\r\n        throw new Error(\r\n          `Attempting to access a type (${name}) that doesn't exist inside one of your ModelType subclasses`\r\n        )\r\n      return target[name]\r\n    },\r\n  }\r\n)\r\n```"}}
{"actor":{"display_login":"timhuff"}}