Define Relations
In This Section We will list the rules for defining a relation between entities, but if you like you can skip to the guide for each relation type below. But please look at the highlighted parts. To define a relation between two entities:
- Define a property in both entities with the type of the other entity.
- Annotate the properties with Relation.
- RelationOptions:
- name: an unique arbitrary string that could describes the relation and the sides.
- The Two properties on both side should share the same relation name.
- Different relations should have different names.
- The name could be something like "{side1}-{side2}" so "user-profile" or "student-courses".
- This allows you to have multiple relations between the same data types. So for example, if a user has two profiles [ "secret profile", "public profile" ] you could have 2 properties with the same data types but with different relation names.
- relation:
- Should be one of the values in the enum RelationTypes.
- It doesn't have to be defined on both sides, just one side is enough.
- type: the data type of the property that is decorated, this is required because of some limitations in the Reflection API in Javascript.
- Due to cyclic dependency the type of the property should a function that returns the desired type. so
()=> PropertyType
- In the type parameter in the RelationOptions ignore the Array brackets in case of
Many
- Due to cyclic dependency the type of the property should a function that returns the desired type. so
- master:
- One of the sides should be the main (master) side and the other would secondary (slave).
- In Symmetric Relations (One to One, Many to Many) the order of the master and slave doesn't matter.
- But in Asymmetric Relations (One to Many) the master should be the
One
side and the slave should be theMany
side. - This parameter is what decides which is the master and which is the slave. it should be equal to true on the master side. the other side could be left unassigned or equal false
- name: an unique arbitrary string that could describes the relation and the sides.
- RelationOptions:
Due to cyclic dependency the type of the property should a function that returns the desired type. so ()=> PropertyType
It is a good practice to use constant variables as the names for the relations, this way you don't have to worry that both side have the exact same string. you can see that in the full example, Constants were used.
One way to handle the constants is to define them on the main Entity File with snake case. For example;
export const USER_PROFILE = "user-profile"
you can see it also in the example below.
// User
export const USER_PROFILE = "user-profile"
@FrostEntity(...)
class User extends FrostObject<User>{
...
@Relation(
{
name: "User-Profile",
relation: RelationTypes.ONE_TO_ONE,
type: ()=> Profile,
}
)
profile: () => Profile
...
}
// Profile
import {User, USER_PROFILE} from "./user"
@FrostEntity(...)
class Profile extends FrostObject<Profile>{
...
@Relation(
{
name: USER_PROFILE,
relation: RelationTypes.ONE_TO_ONE, //optional since it's already assigned in User Class
type: () => User,
}
)
user: ()=> User
...
}
One To One
We'll Use an example of User and Contact Info to explain the process of defining a one to one relation.
- RelationOptions:
- name:
- an unique arbitrary string that could describes the relation and the sides.
- In this example, We wil use "userContactInfo"
- relation:
- the value will be RelationTypes.ONE_TO_ONE.
- you can assign it on either or both sides.
- In this example, We will assign it on the User side.
- type:
- Due to cyclic dependency and some limitations in the Reflection API in Javascript. you will have to pass the type as a function that returns the type of the property in the decorator
- In this example, it will be
()=>ContactInfo
on the User side and()=>User
on the ContactInfo side.
- master: is not required in this case.
- name:
import {ContactInfo} from './contact-info'@FrostEntity({collectionPath:'/users'})export class User extends FrostObject<User>{ id!:string; name!:string; userType!: "Buyer"|"Seller"; @Relation( { name: "userContactInfo", relation: RelationTypes.ONE_TO_ONE, type: ()=> ContactInfo, } ) contactInfo?: () => ContactInfo;}
import {User} from './user'@FrostEntity({collectionPath:'/contact_info'})class ContactInfo extends FrostObject<ContactInfo>{ id!:string; email?:string; address?:string; location?:{lat:string,lng:string}; postalCode?:string; phones?:{ work?:string, home?:string }; landlines?:{ work?:string, home?:string }; @Relation( { name: "userContactInfo", type: () => User, } ) user?: ()=> User}
One To Many
We'll Use an example of Professors and Courses to explain the process of defining a One to Many relation (one professor could teach multiple courses and each course only has one professor).
- RelationOptions:
- name:
- an unique arbitrary string that could describes the relation and the sides.
- In this example, We wil use
professor_courses
- relation:
- the value will be RelationTypes.ONE_TO_MANY.
- you can assign it on either or both sides.
- In this example, We will assign it on the Professor side.
- type:
- Due to cyclic dependency and some limitations in the Reflection API in Javascript. you will have to pass the type as a function that returns the type of the property in the decorator
- In this example, it will be
()=>Course
on the Professor side and()=>Professor
on the Course side. - The property types however should be
()=>Course[]
and()=>Professor
respectively
- master:
- Should be assigned as true on the professor side.
- On the Course side, it should be unassigned(ie;
undefined
) orfalse
- name:
import {Course} from './course'@FrostEntity({collectionPath:'/professors'})export class Professor extends FrostObject<Professor>{ id!:string; name!:string; department!:string; @Relation( { name: "professor_courses", relation: RelationTypes.ONE_TO_MANY, type: ()=> Course, } ) courses?: () => Course[];}
import {Professor} from './professor'@FrostEntity({collectionPath:'/courses'})class Course extends FrostObject<Course>{ id!:string; name!:string; difficultyLevel!:number; duration!: number; // in weeks @Relation( { name: "professor_courses", type: () => Professor, } ) professor?:() => Professor}
Many To Many
Many to Many is very similar to One To One both the properties type return arrays of the entity type.
We'll continue on the same example from One To Many.
The relation we will use to explain the process of defining a Many to Many relation is Student and Courses
(one student could enroll in multiple courses and one course could have multiple students auditing it).
- RelationOptions:
- name:
- an unique arbitrary string that could describes the relation and the sides.
- In this example, We wil use
students_courses
- relation:
- the value will be RelationTypes.MANY_TO_MANY.
- you can assign it on either or both sides.
- In this example, We will assign it on the Student side.
- type:
- Due to cyclic dependency and some limitations in the Reflection API in Javascript. you will have to pass the type as a function that returns the type of the property in the decorator
- In this example, it will be
()=>Course
on the Student side and()=>Student
on the Course side. - The property types however should be
()=>Course[]
and()=>Student[]
respectively
- master: is not required in this case.
- name:
import {Course} from './course'@FrostEntity({collectionPath:'/students'})export class Student extends FrostObject<Student>{ id!:string; name!:string; year: "FRESHMAN" | "SOPHOMORE" | "JUNIOR" | "SENIOR" @Relation( { name: "students_courses", relation: RelationTypes.MANY_TO_MANY, type: ()=> Course, } ) courses?: () => Course[];}
import {Student} from './student'import {Professor} from './professor'@FrostEntity({collectionPath:'/courses'})class Course extends FrostObject<Course>{ id!:string; name!:string; difficultyLevel!:number; duration!: number; // in weeks//one to many example (slave side) @Relation( { name: "professor_courses", type: () => Professor, } ) professor?:() => Professor @Relation( { name: "students_courses", type: () => Student, } ) students?: ()=> Student[]}