Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make Single Table Inheritance usable #371

Open
diegonc opened this issue Jun 17, 2020 · 9 comments
Open

feat: make Single Table Inheritance usable #371

diegonc opened this issue Jun 17, 2020 · 9 comments

Comments

@diegonc
Copy link
Contributor

diegonc commented Jun 17, 2020

TypeORM has a mode in which entities in a tree may share the same table as it's ancestor.
It goes something like in the following snippets:

export enum PartyType {
  PtyPerson = "Person",
  PtyOrganization = "Organization",
  PtyGroup = "Group"
}

@Entity()
@TableInheritance({column: {type: 'varchar', name: 'type'}})
export class Party extends BaseModel { ... }

@ChildEntity(PartyType.PtyPerson)
export class Person extends Party { ... }

@ChildEntity(PartyType.PtyGroup)
export class Group extends Party { ... }

Class party can already be implemented by just using the TableInheritance decorator from TypeORM along with Model from warthog.

Child classes however cannot be added as warthog's Model decorator uses Entity. So, I came up with a custom decorator sitting in my code tree which I called ChildModel and is shown below.

const caller = require('caller'); // eslint-disable-line @typescript-eslint/no-var-requires
import * as path from 'path';
import { ObjectType } from 'type-graphql';
import { ObjectOptions } from 'type-graphql/dist/decorators/ObjectType.d';
import { Container } from 'typedi';
import { ChildEntity } from 'typeorm';
import {
  ClassDecoratorFactory,
  ClassType,
  composeClassDecorators,
  generatedFolderPath,
} from 'warthog';

function getMetadataStorage(): any {
  if (!(global as any).WarthogMetadataStorage) {
    // Since we can't use DI to inject this, just call into the container directly
    (global as any).WarthogMetadataStorage = Container.get('MetadataStorage');
  }
  return (global as any).WarthogMetadataStorage;
}

interface ChildModelOptions {
  api?: ObjectOptions;
}

// Allow default TypeORM and TypeGraphQL options to be used
export function ChildModel(discriminatorValue?: any, { api = {} }: ChildModelOptions = {}) {
  // In order to use the enums in the generated classes file, we need to
  // save their locations and import them in the generated file
  const modelFileName = caller();

  // Use relative paths when linking source files so that we can check the generated code in
  // and it will work in any directory structure
  const relativeFilePath = path.relative(generatedFolderPath(), modelFileName);

  const registerModelWithWarthog = (target: ClassType): void => {
    // Save off where the model is located so that we can import it in the generated classes
    getMetadataStorage().addModel(target.name, target, relativeFilePath);
  };

  const factories = [
    ChildEntity(discriminatorValue) as ClassDecoratorFactory,
    ObjectType(api) as ClassDecoratorFactory,
    registerModelWithWarthog as ClassDecoratorFactory
  ];

  return composeClassDecorators(...factories);
}

Maybe it's a good idea to add the decorator to warthog's source tree :-)

@diegonc
Copy link
Contributor Author

diegonc commented Jun 17, 2020

Oh, I just read in typeorm docs it's an experimental feature yet. Maybe it's not as good idea as I thought.

@goldcaddy77
Copy link
Owner

Hey, thanks for the code sample. Do you want to put it in as a PR in the examples folder? Then it’s not “officially supported” but folks have the pattern handy.

@goldcaddy77
Copy link
Owner

Hey @diegonc . I'd still be interested in getting this represented in the examples folder if you're interested in adding it.

@diegonc
Copy link
Contributor Author

diegonc commented May 4, 2021

Hi, where should I put it? Do I just pick the next number and create a new folder?

@goldcaddy77
Copy link
Owner

Yeah, that works. I typically just copy the last folder, re-number the DB name, etc... and gut the model, service and resolver of what's not needed.

@diegonc
Copy link
Contributor Author

diegonc commented May 8, 2021

Sorry I cannot seem to make it work like it did at that time. Even my original project looks broken now. I cannot make Person and Group implement Party at the graphql schema.

Extending from Party doesn't add the implements clause and adding {api: {implements: Party}} to group or person gives an error (I suspect due to the circular dependencies):

$ warthog codegen
TypeError: Cannot read property 'type' of undefined
    at /home/diegonc/dev/test/quasar/wireframe-api/node_modules/type-graphql/dist/schema/schema-generator.js:157:149
    at Array.map (<anonymous>)
    at interfaces (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/type-graphql/dist/schema/schema-generator.js:157:59)
    at resolveThunk (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/definition.js:438:40)
    at defineInterfaces (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/definition.js:619:20)
    at GraphQLObjectType.getInterfaces (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/definition.js:587:31)
    at typeMapReducer (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/schema.js:276:28)
    at typeMapReducer (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/schema.js:286:20)
    at typeMapReducer (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/schema.js:286:20)
    at Array.reduce (<anonymous>)
    at new GraphQLSchema (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/graphql/type/schema.js:145:28)
    at Function.generateFromMetadataSync (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/type-graphql/dist/schema/schema-generator.js:31:24)
    at Function.<anonymous> (/home/diegonc/dev/test/quasar/wireframe-api/node_modules/type-graphql/dist/schema/schema-generator.js:16:33)
    at Generator.next (<anonymous>)
    at /home/diegonc/dev/test/quasar/wireframe-api/node_modules/tslib/tslib.js:115:75
    at new Promise (<anonymous>)

@goldcaddy77
Copy link
Owner

Can you shoot me a link to your branch?

@diegonc
Copy link
Contributor Author

diegonc commented May 9, 2021

This is what I have so far:

https://github.com/diegonc/warthog/tree/t/single-table-inheritance

@goldcaddy77
Copy link
Owner

Note to self to see diff: main...diegonc:t/single-table-inheritance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants