In addition to assigning basic types to model properties, you can assign entire models as properties. This allows for the creation of complex relationships between models. For information on using basic types for properties, refer to model property types.
We’ll explore different types of relationships between models using examples of Person
and Address
models, evolving the definition step by step.
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
}
}
export class Address extends Persist.Type.Model {
static {
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
To define a one-to-one relationship between two models, set a static property in one model as a function that returns the other model. This ensures that the models can be defined in any order, avoiding issues with initialization.
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
this.address = () => Address;
}
}
export class Address extends Persist.Type.Model {
static {
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
Important
Why Use an Arrow Function?
The arrow function allows the model to reference another model that may not have been defined yet. Without it, you might encounter an error like ReferenceError: Cannot access 'Address' before initialization
.
You can extend the previous example by allowing both models to reference each other. This is useful for circular relationships, where querying one model (e.g., Address
) should also allow access to the related model (e.g., Person
).
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
this.address = () => Address;
}
}
export class Address extends Persist.Type.Model {
static {
this.person = () => Person;
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
To model a one-to-many relationship, use Persist.Type.Array
to store an array of related models. For instance, if a Person
can have multiple addresses, this is how it would be defined:
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
this.addresses = () => Persist.Type.Array.of(Address);
}
}
export class Address extends Persist.Type.Model {
static {
this.person = () => Person;
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
This structure allows for querying both the Person and their multiple Address records, while maintaining the ability to retrieve the related person from any address.
In some cases, you may want to model a many-to-many relationship. For example, if multiple people can live at the same address, this type of relationship is ideal.
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
this.addresses = () => Persist.Type.Array.of(Address);
}
}
export class Address extends Persist.Type.Model {
static {
this.people = () => Persist.Type.Array.of(Person);
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
This allows both Person
and Address
models to reference each other as arrays, establishing a many-to-many relationship.
In more complex scenarios, you may want to capture additional information about the relationship itself. For example, when tracking when a person moved to a particular address, you can create an intermediary model (e.g., Abode
) to store this information.
import Persist from "@acodeninja/persist";
export class Person extends Persist.Type.Model {
static {
this.name = Persist.Type.String.required;
this.addresses = () => Persist.Type.Array.of(Abode);
}
}
export class Abode extends Persist.Type.Model {
static {
this.moveInDate = Persist.Type.Date.required;
this.address = () => Address;
this.person = () => Person;
}
}
export class Address extends Persist.Type.Model {
static {
this.people = () => Persist.Type.Array.of(Person);
this.address = Persist.Type.String.required;
this.postcode = Persist.Type.String.required;
}
}
In this setup:
- A
Person
can have multipleAbode
entries (i.e., where they lived and when they moved in). - Each
Abode
links aPerson
to anAddress
, while also recording the move-in date. - An
Address
can still reference multiple people, making this a flexible and more complex relationship model.