Skip to content
This repository has been archived by the owner on May 18, 2024. It is now read-only.

Relationships

Jeongho Nam edited this page Feb 5, 2023 · 5 revisions

Preface

Type safe relationship decorators.

Relationship decorators of safe-typeorm is a little bit different with original typeorm (anyorm), and such different makes the unsafe anyorm to be the real typeorm. Let's see how relationship decorators of safe-typeorm is different with a demo ERD (Entity Relationshiop Diagram).

If you want to see detailed code of model classes, click one of below links:

Belongs.ManyToOne

import * as orm from "typeorm";
import safe from "safe-typeorm";

// composite index, specify foreign key column names
@orm.Index(["bbs_group_id", "bbs_category_id", "created_at"])
@orm.Entity()
export class BbsArticle {
    @safe.Belongs.ManyToOne(
        () => BbsGroup, // parent entity
        "uuid", // parent entity's PK type
        "bbs_group_id", // foreign key field name
        // INDEXED
    )
    public readonly group!: safe.Belongs.ManyToOne<BbsGroup, "uuid">;

    @safe.Belongs.ManyToOne(
        () => BbsCategory, // parent entity
        "uuid", // column type
        "bbs_category_id", // column name
        { index: true, nullable: true }, // nullable option with indexing
    )
    public readonly category!: safe.Belongs.ManyToOne<
        BbsCategory,
        "uuid",
        { nullable: true } // nullable type
    >;

    @orm.Index()
    @orm.CreateDateColumn()
    public readonly created_at!: Date;
}

When declaring M: 1 belongs relationship, you should input 4 parameters into Belongs.ManyToOne() decorator function. Also, its property type declaration requires 2 or 3 generic arguments like below:

  • Decorator function
    • target: function returning target ORM entity class
    • type: PK type of target entity
    • name: name of foreign key column
    • options?: options of the FK column
      • index?: whether the column is indexed or not
      • nullable?: whether the column is nullable or not
  • Type declaration
    • Target: target entity type
    • Type: PK column type of target entity

During definition, do not forget to use index on the foreign key column. It is possible to defnining atomic index by options parameter, and also possible to define composite index by @orm.Index() decorator function with foreign key column name what you've defined.

Also, if you want to define a nullable foreign key column, you should use nullable option both in decorator function and type declaration. Otherwise, you are planning to define a foreign key column as NOT NULL option, you don't need to specify the nullable option.

Belongs.OneToOne

import * as orm from "typeorm";
import safe from "safe-typeorm";

@orm.Entity()
export class BbsAnswerArticle extends safe.Model {
    /* -----------------------------------------------------------
        COLUMNS
    ----------------------------------------------------------- */
    @safe.Belongs.OneToOne(
        () => BbsArticle, // target entity
        (article) => article.answer,
            // accessor from parent entity 
            // enable to omit, but recommend to fill for memoization
        "uuid", // column type
        "id", // colum name
        { primary: true }, // enable to defined as PK
    )
    public readonly base!: safe.Belongs.OneToOne<BbsArticle, "uuid">;

    @safe.Belongs.OneToOne(
        () => BbsQuestionArticle,
        (question) => question.answer,
        "uuid",
        "bbs_question_id",
        // be UK automatically
    )
    public readonly question!: safe.Belongs.OneToOne<
        BbsQuestionArticle,
        "uuid"
    >;
}

When defining 1: 1 belongs relationship, you should input 3 to 5 parameters into Belongs.OneToOne() decorator function. Also, its property type declaration requires 2 generic arguments like below:

  • Decorator function
    • target: function returning target ORM entity class
    • reverse?: accessor from parent entity
    • type: PK type of target entity
    • name: name of foreign key column
    • options?: options of the FK column
      • primary?: whether the column is primary key or not
      • unique?: whether the column is unique key or not
  • Type declaration
    • Target: target entity type
    • Type: PK column type of target entity

During definition, it is possible to define a 1: 1 belongs foreign key column to be a primary key. Otherwise, you do not define both primary and unique options, the foreign key column would be defined as a unique key constraint automatically.

For reference, if you load the parent entity and reverse parameter has been defined in the decoratur function, current entity record would be memoized into the accessor of parent entity. Therefore, current entity would not be duplicated loaded when parent entity calls the reverse accessor. It is the reason why I recommend you not to omit the reverse parameter for such memoization.

Has.OneToOne

import * as orm from "typeorm";
import safe from "safe-typeorm";

@orm.Entity()
export class BbsArticle extends safe.Model {
    @safe.Has.OneToOne(
        () => BbsQuestionArticle, // target child entity
        (question) => question.base, // accessor from child entity
        // when omitted, child entity may not exists
    )
    public readonly question!: safe.Has.OneToOne<BbsQuestionArticle>;

    @safe.Has.OneToOne(
        () => __MvBbsArticleLastContent, 
        (material) => material.article,
        true, // ensure that the child entity record always exists
    )
    public readonly __mv_last!: safe.Has.OneToOne<
        __MvBbsArticleLastContent,
        true, // declare in type declaration, too
    >;
}

When declaring 1: 1 has relationship, you should input 2 to 3 parameters into Has.OneToOne() decorator function. Also, its property type requires 1 or 2 generic arguments like below:

  • Decorator function
    • target: function returning target ORM entity class
    • reverse: accessor from child entity
    • ensure: whether the child entity record always exists or not, default is false
  • Type declaration
    • Target: target entity type
    • Ensure: whether the child entity record always exists or not, default is false

During definition, you can define whether the child entity record always exists or not, through ensure parameter. If you define ensure parameter as true, it means that the child entity record always exists. Otherwise, you define it as false or omit, the child entity record may not exist.

Has.OneToMany

import * as orm from "typeorm";
import safe from "safe-typeorm";

@orm.Entity()
export class BbsArticle extends safe.Model {
    @safe.Has.OneToMany(
        () => BbsArticleComment, 
        (comment) => comment.article
    )
    public readonly comments!: safe.Has.OneToMany<BbsArticleComment>;

    @safe.Has.OneToMany(
        () => BbsArticleContent, // child entity 
        (content) => content.article, // accessor from child
        (x, y) => x.created_at.getTime() - y.created_at.getTime(),
            // comparator for sorting
    )
    public readonly contents!: safe.Has.OneToMany<BbsArticleContent>;
}

Has.ManyToMany

import * as orm from "typeorm";
import safe from "safe-typeorm";

export class BbsArticleContent extends safe.Model {
    @safe.Has.ManyToMany(
        () => AttachmentFile, // target entity
        () => BbsArticleContentFile, // router entity
        (router) => router.content, // accessor from router to currnet
        (router) => router.file, // accessor from router to target
        (x, y) => x.router.sequence - y.router.sequence,
            // comparator for sorting
    )
    public readonly files!: safe.Has.ManyToMany<
        AttachmentFile, 
        BbsArticleContentFile
    >;
}

@orm.Unique(["bbs_article_content_id", "attachment_file_id"])
@orm.Entity()
export class BbsArticleContentFile extends safe.Model {
    /* -----------------------------------------------------------
        COLUMNS
    ----------------------------------------------------------- */
    @orm.PrimaryGeneratedColumn("uuid")
    public readonly id!: string;

    @safe.Belongs.ManyToOne(
        () => BbsArticleContent,
        "uuid",
        "bbs_article_content_id",
        // INDEXED
    )
    public readonly content!: safe.Belongs.ManyToOne<BbsArticleContent, "uuid">;

    @safe.Belongs.ManyToOne(
        () => AttachmentFile,
        "uuid",
        "attachment_file_id",
        { index: true },
    )
    public readonly file!: safe.Belongs.ManyToOne<AttachmentFile, "uuid">;

    @orm.Column("int")
    public readonly sequence!: number;
}