Relationships - samchon/safe-typeorm GitHub Wiki
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:
- Main entities
- Sub-type entities
- Systematics
- Pair relationships
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 classtype
: PK type of target entityname
: name of foreign key columnoptions?
: options of the FK columnindex?
: whether the column is indexed or notnullable?
: whether the column is nullable or not
- Type declaration
Target
: target entity typeType
: 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.
const article: BbsArticle;
// GET PARENT ENTITY
const group: BbsGroup = await article.group.get();
const category: BbsCategory | null = await article.category.get();
// SET PARENT ENTITY
await article.group.set(group);
await article.category.set(category);
await article.category.set(null);
// GET PARENT ID
const group_id: string = article.group.id;
const category_id: string | null = article.category.id;
After declaration, you can access to the parent entity by get()
method. The get()
is a type of singleton method that loading parent entity record only one time by caching. In contrary, configuring parent entity by set()
method is possible, too. Also, you can access to the foreign key column value by id
property.
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 classreverse?
: accessor from parent entitytype
: PK type of target entityname
: name of foreign key columnoptions?
: options of the FK columnprimary?
: whether the column is primary key or notunique?
: whether the column is unique key or not
- Type declaration
Target
: target entity typeType
: 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.
const answer: BbsAnswerArticle;
// GET PARENT ENTITY
const question: BbsQuestionArticle = await answer.get();
const article: BbsArticle = await answer.base.get();
// SET PARENT ENTITY
await answer.set(question);
await answer.base.set(article);
// GET PARENT ID
const question_id: string = answer.question.id;
const article_id: string = answer.base.id;
After declaration, you can access to the parent entity by get()
method. The get()
is a type of singleton method that loading parent entity record only one time by caching. In contrary, configuring parent entity by set()
method is possible, too. Also, you can access to the foreign key column value by id
property.
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 classreverse
: accessor from child entityensure
: whether the child entity record always exists or not, default isfalse
- Type declaration
Target
: target entity typeEnsure
: whether the child entity record always exists or not, default isfalse
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.
const article: BbsArticle;
// GET CHILD ENTITY
const question: BbsQuestionArticle | null = await article.question.get();
const last: __MvBbsArticleLastContent = await article.__mv_last.get();
// SET CHILD ENTITY
await article.question.set(question);
await article.__mv_last.set(last);
After declaration, you can access to the child entity by get()
method. The get()
is a type of singleton method that loading child entity record only one time by caching. In contrary, configuring child entity by set()
method is possible, too.
Has.OneToMany
import * as orm from "typeorm";
import safe from "safe-typeorm";
@orm.Entity()
export class BbsArticle extends safe.Model {
@safe.Has.OneToMany(
() => BbsArticleComment, // child entity
(comment) => comment.article // accessor from child
)
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>;
}
When defining 1: N has relationship, you should input 2 or 3 parameters into Has.OneToMany()
decorator function. Also, its property type declaration requires only one generic argument Target
like below:
- Decorator function
target
: function returning target ORM entity classreverse
: accessor from child entitycomparator?
: comparator function for sorting
- Type declaration
Target
: target entity type
const article: BbsArticle;
// GET CHILDREN ENTITIES
const comments: BbsArticleComment[] = await article.comments.get();
const contents: BbsArticleContent[] = await article.contents.get();
// SET CHILDREN ENTITIES
await article.comments.set(comments);
await article.contents.set(contents);
After declaration, you can access to the children records by get()
method. The get()
is a type of singleton method that loading children records only one time by caching. In contrary, configuring children records by set()
method is possible, too.
For reference, above contents
would be sorted by creation time when loading by get()
method, because it has a comparator function. However, such sorting would not be automatically worked when you configure children records by set()
method.
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.file, // accessor from router to target
(router) => router.content, // accessor from router to currnet
(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;
}
When defining M: N has relationship, you have to declare router entity class like above BbsArticleContentFile
. After that, you should input 4 or 5 parameters into Has.ManyToMany()
decorator function. Also, its property type declaration requires 2 generic arguments like below:
- Decorator functions
target
: function returning target ORM entity classrouter
: function returning router ORM entity classtargetReverse
: accessor from router entity to target entitymyReverse
: accessor from router entity to current entitycomparator?
: comparator function for sorting
- Type declaration
Target
: target entity typeRouter
: router entity type
const content: BbsArticleContent;
// GET CHILDREN ENTITIES
const files: AttachmentFile[] = await content.files.get();
// SET CHILDREN ENTITIES
await content.files.set(files);
After declaration, you can access to the children records by get()
method. The get()
is a type of singleton method that loading children records only one time by caching. In contrary, configuring children records by set()
method is possible, too.
For reference, above files
would be sorted by creation time when loading by get()
method, because it has a comparator function. However, such sorting would not be automatically worked when you configure children records by set()
method.