{"action":"created","issue":{"url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/22","repository_url":"https://api.github.com/repos/silverstripe/silverstripe-graphql","labels_url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/22/labels{/name}","comments_url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/22/comments","events_url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/22/events","html_url":"https://github.com/silverstripe/silverstripe-graphql/issues/22","id":195427981,"number":22,"title":"Scaffold object type inheritance","user":{"login":"chillu","id":111025,"avatar_url":"https://avatars0.githubusercontent.com/u/111025?v=3","gravatar_id":"","url":"https://api.github.com/users/chillu","html_url":"https://github.com/chillu","followers_url":"https://api.github.com/users/chillu/followers","following_url":"https://api.github.com/users/chillu/following{/other_user}","gists_url":"https://api.github.com/users/chillu/gists{/gist_id}","starred_url":"https://api.github.com/users/chillu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chillu/subscriptions","organizations_url":"https://api.github.com/users/chillu/orgs","repos_url":"https://api.github.com/users/chillu/repos","events_url":"https://api.github.com/users/chillu/events{/privacy}","received_events_url":"https://api.github.com/users/chillu/received_events","type":"User","site_admin":false},"labels":[{"id":443455056,"url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/labels/type/enhancement","name":"type/enhancement","color":"0e8a16","default":false}],"state":"closed","locked":false,"assignee":{"login":"unclecheese","id":654636,"avatar_url":"https://avatars2.githubusercontent.com/u/654636?v=3","gravatar_id":"","url":"https://api.github.com/users/unclecheese","html_url":"https://github.com/unclecheese","followers_url":"https://api.github.com/users/unclecheese/followers","following_url":"https://api.github.com/users/unclecheese/following{/other_user}","gists_url":"https://api.github.com/users/unclecheese/gists{/gist_id}","starred_url":"https://api.github.com/users/unclecheese/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/unclecheese/subscriptions","organizations_url":"https://api.github.com/users/unclecheese/orgs","repos_url":"https://api.github.com/users/unclecheese/repos","events_url":"https://api.github.com/users/unclecheese/events{/privacy}","received_events_url":"https://api.github.com/users/unclecheese/received_events","type":"User","site_admin":false},"assignees":[{"login":"unclecheese","id":654636,"avatar_url":"https://avatars2.githubusercontent.com/u/654636?v=3","gravatar_id":"","url":"https://api.github.com/users/unclecheese","html_url":"https://github.com/unclecheese","followers_url":"https://api.github.com/users/unclecheese/followers","following_url":"https://api.github.com/users/unclecheese/following{/other_user}","gists_url":"https://api.github.com/users/unclecheese/gists{/gist_id}","starred_url":"https://api.github.com/users/unclecheese/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/unclecheese/subscriptions","organizations_url":"https://api.github.com/users/unclecheese/orgs","repos_url":"https://api.github.com/users/unclecheese/repos","events_url":"https://api.github.com/users/unclecheese/events{/privacy}","received_events_url":"https://api.github.com/users/unclecheese/received_events","type":"User","site_admin":false}],"milestone":null,"comments":9,"created_at":"2016-12-14T02:57:30Z","updated_at":"2017-03-26T21:04:05Z","closed_at":"2017-03-21T22:44:12Z","body":"Related to [Scaffold input type inheritance](https://github.com/silverstripe/silverstripe-graphql/issues/23)\r\n\r\nIn line with ORM conventions, we want to expose fields from all subclasses when querying the base class (e.g. `SiteTree`). In contrast to the ORM, GraphQL exposes a type or interface for fields of the returned objects. While we could flatten all fields to the base type, it means a lot of fields which don't make sense without further context:\r\n\r\n```\r\ntype SiteTree {\r\n    id\r\n    created\r\n    title\r\n    content\r\n    myField <-- from Page\r\n    redirectionType <-- from RedirectorPage\r\n    externalUrl <-- from RedirectorPage\r\n}\r\n```\r\n\r\nInstead, I propose we create distinct types for each subclass, each of which is a superset of its parent class.\r\n\r\n```\r\ntype SiteTree {\r\n    id\r\n    created\r\n    title\r\n    content\r\n}\r\n\r\ntype Page {\r\n    id\r\n    created\r\n    title\r\n    content\r\n    myField\r\n}\r\n\r\ntype RedirectorPage {\r\n    id\r\n    created\r\n    title\r\n    content\r\n    myField\r\n    redirectionType\r\n    externalUrl\r\n}\r\n```\r\n\r\nThis structure allows us to declare multiple types as a [union](http://facebook.github.io/graphql/#sec-Unions) in queries:\r\n\r\n```\r\nunion SiteTreeTypes = SiteTree | Page | RedirectorPage\r\n\r\ntype readSiteTree {\r\n  [SiteTreeTypes]\r\n}\r\n```\r\n\r\nThe downside is that queries will always have to declare on which type they're expecting fields:\r\n\r\n```\r\nquery readSiteTree() {\r\n  ...on SiteTree {\r\n    id\r\n    created\r\n    title\r\n    content\r\n  }\r\n  ...on Page {\r\n    myField\r\n  }\r\n  ...on RedirectorPage {\r\n    redirectionType\r\n    externalUrl\r\n  }\r\n}\r\n```\r\n\r\nWhile a type [can implement](https://github.com/facebook/graphql/issues/48#issuecomment-201089763) more than one interface, a field (query response) can only declare a interface response type (or a union of types) - see [discussion on facebook/graphql](https://github.com/facebook/graphql/issues/48). We could use a single [interface](http://facebook.github.io/graphql/#sec-Interfaces), but there's no \"union of interfaces\" in GraphQL. \r\n\r\nSam [suggested](https://gist.github.com/chillu/d0069fb0123d0480761e6ba5659bd2e7) types implementing multiple interfaces, but given the lack of \"union of interface\" support, I don't see much value in this. Also, it we can't have all types implement a `SiteTreeInterface` to get around writing `...on SiteTree` for fields on the base type - it's either a single interface, or a union of types (verified through a [modified ReadFileQueryCreator](https://gist.github.com/chillu/0461fc0c21eb746b12466e548eae8515)).\r\n\r\nIn general, the recommended approach makes it slightly harder to automatically construct GraphQL queries (e.g. for a dynamic React-based GridField, or for a full data exporter). But since most JS UI logic should know which fields it needs in components, I'd see this more of an edge case than a problem in practice. For example, the CMS tree view would always just query the base attributes on the `SiteTree` type since it doesn't need to display anything else.\r\n\r\nThere's some [tests in the graphql-js reference implementation](https://github.com/graphql/graphql-js/blob/master/src/execution/__tests__/union-interface-test.js) for this, and an interesting [SO discussion on interfaces vs. unions](http://stackoverflow.com/questions/34726666/real-world-example-of-graphqlinterfacetype-and-graphqluniontype). And a nice [Medium posts about interfaces and unions](https://medium.com/the-graphqlhub/graphql-tour-interfaces-and-unions-7dd5be35de0d#.4jacb5kj7)"},"comment":{"url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/comments/289315413","html_url":"https://github.com/silverstripe/silverstripe-graphql/issues/22#issuecomment-289315413","issue_url":"https://api.github.com/repos/silverstripe/silverstripe-graphql/issues/22","id":289315413,"user":{"login":"unclecheese","id":654636,"avatar_url":"https://avatars2.githubusercontent.com/u/654636?v=3","gravatar_id":"","url":"https://api.github.com/users/unclecheese","html_url":"https://github.com/unclecheese","followers_url":"https://api.github.com/users/unclecheese/followers","following_url":"https://api.github.com/users/unclecheese/following{/other_user}","gists_url":"https://api.github.com/users/unclecheese/gists{/gist_id}","starred_url":"https://api.github.com/users/unclecheese/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/unclecheese/subscriptions","organizations_url":"https://api.github.com/users/unclecheese/orgs","repos_url":"https://api.github.com/users/unclecheese/repos","events_url":"https://api.github.com/users/unclecheese/events{/privacy}","received_events_url":"https://api.github.com/users/unclecheese/received_events","type":"User","site_admin":false},"created_at":"2017-03-26T21:04:05Z","updated_at":"2017-03-26T21:04:05Z","body":"If the fields are common to all types, you don't need `...on`, so I think we're okay, no?\r\n\r\n```\r\nclass A extends DataObject {\r\n  private static $db = ['Title' => 'Varchar'];\r\n}\r\n```\r\n\r\n```\r\nread A {\r\n  Title\r\n}\r\n```\r\n\r\n```\r\nclass B extends A {\r\n  private static $db = ['SecondTitle' => 'Varchar'];\r\n}\r\n```\r\n\r\n```\r\nread A {\r\n  Title\r\n}\r\n```\r\n^ Still works.\r\n\r\n```\r\nread A {\r\n  Title\r\n  SecondTitle\r\n}\r\n```\r\n^ fails\r\n\r\n```\r\nread A {\r\n  Title\r\n  ..on B {\r\n     SecondTitle\r\n  }\r\n}\r\n```\r\n^ works\r\n"}}
{"actor":{"display_login":"unclecheese"}}