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

Example of Bridge Tables #454

Closed
kferrone opened this issue Sep 10, 2018 · 8 comments
Closed

Example of Bridge Tables #454

kferrone opened this issue Sep 10, 2018 · 8 comments

Comments

@kferrone
Copy link

Would be nice to see an example of how to make a proper bridge table relationship (aka Many to Many) using sequelize-typescript. Since no examples, I must trial and error till I figure it out. Wish me luck . . .

@RobinBuschmann
Copy link
Member

@kferrone
Copy link
Author

So I was definitely a little blind and missed that section, thank you.

Unfortunately these dang bridge tables are just not working for me. I can't get them to work both directions. I can only ever get one of the two models to accept a BelongsToMany annotation.

Basically I can have a Contact with abilities, or I can have an Ability with contacts but I can't have both or else I get an error like below if I have the BelongsToMany on both:

TypeError: Cannot read property 'isInitialized' of undefined

Here is my codes:

contact.model.ts

import {Ability, ContactAbility} from "./";
import {Table, Column, Model, DataType, PrimaryKey, AutoIncrement, Comment, ForeignKey, BelongsTo, BelongsToMany} from 'sequelize-typescript';

/**
 * A directory of contacts who can be called upon for battle. 
 */
@Table({
  tableName: 'contact',
  underscoredAll: true,
  underscored: true,
  comment: 'A list of people who can be contacted'
})
export default class Contact extends Model<Contact> {

  /**
   * A unique auto incrementing ID to identify a certain individual. 
   */
  @AutoIncrement
  @PrimaryKey
  @Comment('The tables primary key')
  @Column
  get id(): number {
    return this.getDataValue('id') as number;
  }

  /**
   * The first or given name of the individual. 
   */
  @Column({
    field: 'first_name',
    comment: 'The first name of the contact'
  })
  public firstName: string;

  /**
   * Last name or surname of the individual. 
   */
  @Column({
    field: 'last_name',
    comment: 'The contacts last name'
  })
  public lastName: string;

  /**
   * Each contact has a message they want to share. 
   */
  @Column(DataType.TEXT)
  public message: string;

  /**
   * A list of super powered abilities this contact poses. 
   */
  @BelongsToMany(() => Ability, () => ContactAbility, 'contactID', 'abilityID')
  public abilities: Ability[];

}

ability.model.ts

import {Contact, ContactAbility} from "./";
import {Table, Column, Model, DataType, PrimaryKey, Comment, Is, BelongsToMany} from 'sequelize-typescript';

/**
 * All of the super powers a super powered contact may have. 
 */
@Table({
  tableName: 'ability',
  underscoredAll: true,
  underscored: true,
  comment: 'All the superpowers someone may have.'
})
export default class Ability extends Model<Ability> {

  /**
   * A stringified representation to quickly identify the ability. 
   */
  @PrimaryKey
  @Comment('The tables primary key')
  @Column
  public id: string;

  /**
   * A super cool and super powered title for the ability. 
   */
  @Column({
    comment: 'The name of this ability',
    unique: true
  })
  public title: string;

  /**
   * Describes how the ability works and any other useful bits of info to understand. 
   */
  @Column({
    type: DataType.TEXT,
    comment: 'A description of how this ability works.'
  })
  public description: string;

  /**
   * A standard way of determining how powerful an ability is.  
   * Basically on a scale of one to ten how powerful is this ability? 
   */
  @Is('oneToTen', (powerLevel: number) => {
    if (!((powerLevel >= 1) && (powerLevel <= 10))) {return new Error(`${powerLevel} must be a value between 1 and 10`);}
  })
  @Column({
    field: 'power_level',
    type: DataType.INTEGER
  })
  public powerLevel: number;

  /**
   * A list of super powered contacts who possess a certain ability. 
   */
   // if uncommented there will be errors
  /*@BelongsToMany(() => Contact, () => ContactAbility, 'abilityID', 'contactID')
  public contacts: Contact[];*/
}

contactAbility.model.ts

import {Table, Column, Model, ForeignKey} from 'sequelize-typescript';
import {Contact, Ability} from "./";

/**
 * A bridge table to reference the abilities each contact can have and what contacts are applied to an ability. 
 */
@Table({
  tableName: 'contact_ability',
  underscoredAll: true,
  underscored: true,
  comment: 'All the superpowers a certain contact has.'
})
export default class ContactAbility extends Model<ContactAbility> {

  /**
   * A reference to a contact who has a certain ability. 
   */
  @ForeignKey(() => Contact)
  @Column({
    field: 'contact_id',
    primaryKey: true
  })
  public contactID: number;

  /**
   * A reference to the ability a certain contact has. 
   */
  @ForeignKey(() => Ability)
  @Column({
    field: 'ability_id',
    primaryKey: true
  })
  public abilityID: string;

}

Please help me understand what is wrong?!?!

@RobinBuschmann
Copy link
Member

RobinBuschmann commented Sep 12, 2018

Hey @kferrone, can you share your repo with me, so that I can check?

I assume it has something to do with where you are importing your models from: ...from './'. But this is just a guess

@kferrone
Copy link
Author

OK so I can't give the full project I'm on at work but I did simplify what I'm doing down to just using sequelize-typescript.

Here is the repo I made real quick this morning: kferrone/sequel_fun

The repo shows off perfectly my issue if you were to un-comment the lines at the bottom of the ability.model.ts where you see the @BelongsToMany above contacts.

@kferrone
Copy link
Author

OK .... Your assumption about where I was importing from is correct, i.e. import { . . . } from './'. Why the heck would that be an issue??? All I did was export the default classes in the index like below.

src/models/index.ts

export {default as Contact} from './contact.model';
export {default as Key} from './key.model';
export {default as Ability} from './ability.model';
export {default as ContactAbility} from './contactAbility.model';
export {default as Guild} from './guild.model';

Is this an issue because they become an alias when I export an import and sequelize doesn't like that?

@RobinBuschmann
Copy link
Member

RobinBuschmann commented Sep 13, 2018

For instance if you define a relation between two models with BelongsTo and HasMany, you will result in a circular dependency between both models. In general node can handle this, as long as the circular references aren't resolved immediately like

// a.model.ts
import {B} from './b.model.ts';
new B();
// b.model.ts
import {A} from './a.model.ts';
new A();

This example throws an error. With your index.ts a similar thing happens. I need some time to check what the exact issue is.
That's why sequelize-typescript uses arrow functions to reference the dependencies “later”.

@RobinBuschmann
Copy link
Member

@kferrone Does my suggestion already helped you?

@kferrone
Copy link
Author

Oh yeah everything worked fine. My issue was indeed how I was importing. You do have to import the referenced entities directly from their files, i.e. no redirecting through index.js. Thank you very much.

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