Start implementation of model relations
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -3,7 +3,7 @@ import {Container, Inject, Instantiable, StaticClass} from '../../di'
|
||||
import {DatabaseService} from '../DatabaseService'
|
||||
import {ModelBuilder} from './ModelBuilder'
|
||||
import {getFieldsMeta, ModelField} from './Field'
|
||||
import {deepCopy, Pipe, Collection, Awaitable, uuid4} from '../../util'
|
||||
import {deepCopy, Pipe, Collection, Awaitable, uuid4, isKeyof} from '../../util'
|
||||
import {EscapeValueObject} from '../dialect/SQLDialect'
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {Logging} from '../../service/Logging'
|
||||
@@ -17,6 +17,10 @@ import {ModelUpdatedEvent} from './events/ModelUpdatedEvent'
|
||||
import {ModelCreatingEvent} from './events/ModelCreatingEvent'
|
||||
import {ModelCreatedEvent} from './events/ModelCreatedEvent'
|
||||
import {EventBus} from '../../event/EventBus'
|
||||
import {Relation, RelationValue} from './relation/Relation'
|
||||
import {HasOne} from './relation/HasOne'
|
||||
import {HasMany} from './relation/HasMany'
|
||||
import {HasOneOrMany} from './relation/HasOneOrMany'
|
||||
|
||||
/**
|
||||
* Base for classes that are mapped to tables in a database.
|
||||
@@ -95,6 +99,12 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
*/
|
||||
protected modelEventBusSubscribers: Collection<EventSubscriberEntry<any>> = new Collection<EventSubscriberEntry<any>>()
|
||||
|
||||
/**
|
||||
* Cache of relation instances by property accessor.
|
||||
* This is used by the `@Relation()` decorator to cache Relation instances.
|
||||
*/
|
||||
public relationCache: Collection<{ accessor: string | symbol, relation: Relation<T, any, any> }> = new Collection<{accessor: string | symbol; relation: Relation<T, any, any>}>()
|
||||
|
||||
/**
|
||||
* Get the table name for this model.
|
||||
*/
|
||||
@@ -871,4 +881,152 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new one-to-one relation instance. Should be called from a method on the model:
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyModel extends Model<MyModel> {
|
||||
* @Related()
|
||||
* public otherModel() {
|
||||
* return this.hasOne(MyOtherModel)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param related
|
||||
* @param foreignKeyOverride
|
||||
* @param localKeyOverride
|
||||
*/
|
||||
public hasOne<T2 extends Model<T2>>(related: Instantiable<T2>, foreignKeyOverride?: keyof T & string, localKeyOverride?: keyof T2 & string): HasOne<T, T2> {
|
||||
return new HasOne<T, T2>(this as unknown as T, this.make(related), foreignKeyOverride, localKeyOverride)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new one-to-one relation instance. Should be called from a method on the model:
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyModel extends Model<MyModel> {
|
||||
* @Related()
|
||||
* public otherModels() {
|
||||
* return this.hasMany(MyOtherModel)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param related
|
||||
* @param foreignKeyOverride
|
||||
* @param localKeyOverride
|
||||
*/
|
||||
public hasMany<T2 extends Model<T2>>(related: Instantiable<T2>, foreignKeyOverride?: keyof T & string, localKeyOverride?: keyof T2 & string): HasMany<T, T2> {
|
||||
return new HasMany<T, T2>(this as unknown as T, this.make(related), foreignKeyOverride, localKeyOverride)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the inverse of a one-to-one relation. Should be called from a method on the model:
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyModel extends Model<MyModel> {
|
||||
* @Related()
|
||||
* public otherModel() {
|
||||
* return this.hasOne(MyOtherModel)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class MyOtherModel extends Model<MyOtherModel> {
|
||||
* @Related()
|
||||
* public myModel() {
|
||||
* return this.belongsToOne(MyModel, 'otherModel')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param related
|
||||
* @param relationName
|
||||
*/
|
||||
public belongsToOne<T2 extends Model<T2>>(related: Instantiable<T>, relationName: keyof T2): HasOne<T, T2> {
|
||||
const relatedInst = this.make(related) as T2
|
||||
const relation = relatedInst.getRelation(relationName)
|
||||
|
||||
if ( !(relation instanceof HasOneOrMany) ) {
|
||||
throw new TypeError(`Cannot create belongs to one relation. Inverse relation must be HasOneOrMany.`)
|
||||
}
|
||||
|
||||
const localKey = relation.localKey
|
||||
const foreignKey = relation.foreignKey
|
||||
|
||||
if ( !isKeyof(localKey, this as unknown as T) || !isKeyof(foreignKey, relatedInst) ) {
|
||||
throw new TypeError('Local or foreign keys do not exist on the base model.')
|
||||
}
|
||||
|
||||
return new HasOne<T, T2>(this as unknown as T, relatedInst, localKey, foreignKey)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the inverse of a one-to-many relation. Should be called from a method on the model:
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyModel extends Model<MyModel> {
|
||||
* @Related()
|
||||
* public otherModels() {
|
||||
* return this.hasMany(MyOtherModel)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* class MyOtherModel extends Model<MyOtherModel> {
|
||||
* @Related()
|
||||
* public myModels() {
|
||||
* return this.belongsToMany(MyModel, 'otherModels')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param related
|
||||
* @param relationName
|
||||
*/
|
||||
public belongsToMany<T2 extends Model<T2>>(related: Instantiable<T>, relationName: keyof T2): HasMany<T, T2> {
|
||||
const relatedInst = this.make(related) as T2
|
||||
const relation = relatedInst.getRelation(relationName)
|
||||
|
||||
if ( !(relation instanceof HasOneOrMany) ) {
|
||||
throw new TypeError(`Cannot create belongs to one relation. Inverse relation must be HasOneOrMany.`)
|
||||
}
|
||||
|
||||
const localKey = relation.localKey
|
||||
const foreignKey = relation.foreignKey
|
||||
|
||||
if ( !isKeyof(localKey, this as unknown as T) || !isKeyof(foreignKey, relatedInst) ) {
|
||||
throw new TypeError('Local or foreign keys do not exist on the base model.')
|
||||
}
|
||||
|
||||
return new HasMany<T, T2>(this as unknown as T, relatedInst, localKey, foreignKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relation instance returned by a method on this model.
|
||||
* @param name
|
||||
* @protected
|
||||
*/
|
||||
protected getRelation<T2 extends Model<T2>>(name: keyof this): Relation<T, T2, RelationValue<T2>> {
|
||||
const relFn = this[name]
|
||||
|
||||
if ( relFn instanceof Relation ) {
|
||||
return relFn
|
||||
}
|
||||
|
||||
if ( typeof relFn === 'function' ) {
|
||||
const rel = relFn.apply(relFn, this)
|
||||
if ( rel instanceof Relation ) {
|
||||
return rel
|
||||
}
|
||||
}
|
||||
|
||||
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return a Relation.`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user