import ModelQuery, {ModelQueryResult, WhereTest} from "./ModelQuery"; import Model, {ModelClass} from "./Model"; export default abstract class ModelRelation { protected readonly model: S; protected readonly foreignModelClass: ModelClass; protected readonly query: ModelQuery; protected cachedModels?: R; protected constructor(model: S, foreignModelClass: ModelClass) { this.model = model; this.foreignModelClass = foreignModelClass; this.query = this.foreignModelClass.select(); } public abstract clone(): ModelRelation; public constraint(queryModifier: QueryModifier): this { queryModifier(this.query); return this; } public abstract getModelID(): any; protected abstract async compute(query: ModelQuery): Promise; public async get(): Promise { if (this.cachedModels === undefined) { this.cachedModels = await this.compute(this.query); } return this.cachedModels; } public abstract async eagerLoad(relations: ModelRelation[]): Promise>; public abstract async populate(models: ModelQueryResult): Promise; } export type QueryModifier = (query: ModelQuery) => ModelQuery; export class OneModelRelation extends ModelRelation { protected readonly dbProperties: RelationDatabaseProperties; constructor(model: S, foreignModelClass: ModelClass, dbProperties: RelationDatabaseProperties) { super(model, foreignModelClass); this.dbProperties = dbProperties; } public clone(): OneModelRelation { return new OneModelRelation(this.model, this.foreignModelClass, this.dbProperties); } public getModelID() { return this.model[this.dbProperties.localKey]; } protected async compute(query: ModelQuery): Promise { this.query.where(this.dbProperties.foreignKey, this.getModelID()); return await query.first(); } public async eagerLoad(relations: ModelRelation[]): Promise> { this.query.where( this.dbProperties.foreignKey, relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined), WhereTest.IN ); return await this.query.get(); } public async populate(models: ModelQueryResult): Promise { this.cachedModels = models.filter(m => m[this.dbProperties.foreignKey] === this.getModelID())[0] || null; } } export class ManyModelRelation extends ModelRelation { protected readonly dbProperties: RelationDatabaseProperties; constructor(model: S, foreignModelClass: ModelClass, dbProperties: RelationDatabaseProperties) { super(model, foreignModelClass); this.dbProperties = dbProperties; } public clone(): ManyModelRelation { return new ManyModelRelation(this.model, this.foreignModelClass, this.dbProperties); } public cloneReduceToOne(): OneModelRelation { return new OneModelRelation(this.model, this.foreignModelClass, this.dbProperties); } public getModelID(): any { return this.model[this.dbProperties.localKey]; } protected async compute(query: ModelQuery): Promise { this.query.where(this.dbProperties.foreignKey, this.getModelID()); return await query.get(); } public async eagerLoad(relations: ModelRelation[]): Promise> { this.query.where( this.dbProperties.foreignKey, relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined), WhereTest.IN ); return await this.query.get(); } public async populate(models: ModelQueryResult): Promise { this.cachedModels = models.filter(m => m[this.dbProperties.foreignKey] === this.getModelID); } } export class ManyThroughModelRelation extends ModelRelation { protected readonly dbProperties: PivotRelationDatabaseProperties; constructor(model: S, foreignModelClass: ModelClass, dbProperties: PivotRelationDatabaseProperties) { super(model, foreignModelClass); this.dbProperties = dbProperties; this.query .leftJoin(`${this.dbProperties.pivotTable} as pivot`) .on(`pivot.${this.dbProperties.foreignPivotKey}`, `${this.foreignModelClass.table}.${this.dbProperties.foreignKey}`); } public clone(): ManyThroughModelRelation { return new ManyThroughModelRelation(this.model, this.foreignModelClass, this.dbProperties); } public getModelID(): any { return this.model[this.dbProperties.localKey]; } protected async compute(query: ModelQuery): Promise { this.query.where(`pivot.${this.dbProperties.localPivotKey}`, this.getModelID()); return await query.get(); } public async eagerLoad(relations: ModelRelation[]): Promise> { this.query.where( `pivot.${this.dbProperties.localPivotKey}`, relations.map(r => r.getModelID()), WhereTest.IN); this.query.pivot(`pivot.${this.dbProperties.localPivotKey}`, `pivot.${this.dbProperties.foreignPivotKey}`); return await this.query.get(); } public async populate(models: ModelQueryResult): Promise { const ids = models.pivot! .filter(p => p[this.dbProperties.localPivotKey] === this.getModelID()) .map(p => p[this.dbProperties.foreignPivotKey]); this.cachedModels = models.filter(m => ids.indexOf(m[this.dbProperties.foreignKey]) >= 0); } } export type RelationDatabaseProperties = { localKey: string; foreignKey: string; }; export type PivotRelationDatabaseProperties = RelationDatabaseProperties & { pivotTable: string; localPivotKey: string; foreignPivotKey: string; };