ModelRelation: add pagination

This commit is contained in:
Alice Gaudon 2020-09-04 18:01:29 +02:00
parent 8358c3fd4e
commit 6a4898cbda
2 changed files with 44 additions and 28 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "wms-core", "name": "wms-core",
"version": "0.21.9", "version": "0.21.10-rc.1",
"description": "Node web application framework and toolbelt.", "description": "Node web application framework and toolbelt.",
"repository": "https://gitlab.com/ArisuOngaku/wms-core", "repository": "https://gitlab.com/ArisuOngaku/wms-core",
"author": "Alice Gaudon <alice@gaudon.pro>", "author": "Alice Gaudon <alice@gaudon.pro>",

View File

@ -5,7 +5,6 @@ import ModelFactory from "./ModelFactory";
export default abstract class ModelRelation<S extends Model, O extends Model, R extends O | O[] | null> { export default abstract class ModelRelation<S extends Model, O extends Model, R extends O | O[] | null> {
protected readonly model: S; protected readonly model: S;
protected readonly foreignFactory: ModelFactory<O>; protected readonly foreignFactory: ModelFactory<O>;
private readonly query: ModelQuery<O>;
protected readonly queryModifiers: QueryModifier<O>[] = []; protected readonly queryModifiers: QueryModifier<O>[] = [];
protected readonly filters: ModelFilter<O>[] = []; protected readonly filters: ModelFilter<O>[] = [];
protected cachedModels?: R; protected cachedModels?: R;
@ -13,7 +12,6 @@ export default abstract class ModelRelation<S extends Model, O extends Model, R
protected constructor(model: S, foreignFactory: ModelFactory<O>) { protected constructor(model: S, foreignFactory: ModelFactory<O>) {
this.model = model; this.model = model;
this.foreignFactory = foreignFactory; this.foreignFactory = foreignFactory;
this.query = this.foreignFactory.select();
} }
public abstract clone(): ModelRelation<S, O, R>; public abstract clone(): ModelRelation<S, O, R>;
@ -28,19 +26,23 @@ export default abstract class ModelRelation<S extends Model, O extends Model, R
return this; return this;
} }
protected getFinalQuery(): ModelQuery<O> { protected makeQuery(): ModelQuery<O> {
for (const modifier of this.queryModifiers) modifier(this.query); const query = this.foreignFactory.select();
this.queryModifiers.splice(0, this.queryModifiers.length); // Empty modifier now that they were applied for (const modifier of this.queryModifiers) modifier(query);
return this.query; return query;
} }
public abstract getModelID(): any; public abstract getModelID(): any;
protected abstract async compute(query: ModelQuery<O>): Promise<R>; protected abstract async compute(query: ModelQuery<O>): Promise<R>;
protected abstract applyRegularConstraints(query: ModelQuery<O>): void;
public async get(): Promise<R> { public async get(): Promise<R> {
if (this.cachedModels === undefined) { if (this.cachedModels === undefined) {
this.cachedModels = await this.compute(this.getFinalQuery()); const query = this.makeQuery();
this.applyRegularConstraints(query);
this.cachedModels = await this.compute(query);
} }
return this.cachedModels; return this.cachedModels;
} }
@ -84,10 +86,13 @@ export class OneModelRelation<S extends Model, O extends Model> extends ModelRel
} }
protected async compute(query: ModelQuery<O>): Promise<O | null> { protected async compute(query: ModelQuery<O>): Promise<O | null> {
this.getFinalQuery().where(this.dbProperties.foreignKey, this.getModelID());
return await query.first(); return await query.first();
} }
protected applyRegularConstraints(query: ModelQuery<O>): void {
query.where(this.dbProperties.foreignKey, this.getModelID());
}
public async get(): Promise<O | null> { public async get(): Promise<O | null> {
const model = await super.get(); const model = await super.get();
if (model) { if (model) {
@ -104,7 +109,7 @@ export class OneModelRelation<S extends Model, O extends Model> extends ModelRel
const ids = relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined); const ids = relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined);
if (ids.length === 0) return []; if (ids.length === 0) return [];
const query = this.getFinalQuery(); const query = this.makeQuery();
query.where(this.dbProperties.foreignKey, ids, WhereTest.IN); query.where(this.dbProperties.foreignKey, ids, WhereTest.IN);
return await query.get(); return await query.get();
} }
@ -124,6 +129,7 @@ export class OneModelRelation<S extends Model, O extends Model> extends ModelRel
export class ManyModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> { export class ManyModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> {
protected readonly dbProperties: RelationDatabaseProperties; protected readonly dbProperties: RelationDatabaseProperties;
protected readonly paginatedCache: { [perPage: number]: { [pageNumber: number]: ModelQueryResult<O> } } = {};
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) { constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: RelationDatabaseProperties) {
super(model, foreignFactory); super(model, foreignFactory);
@ -143,10 +149,13 @@ export class ManyModelRelation<S extends Model, O extends Model> extends ModelRe
} }
protected async compute(query: ModelQuery<O>): Promise<O[]> { protected async compute(query: ModelQuery<O>): Promise<O[]> {
this.getFinalQuery().where(this.dbProperties.foreignKey, this.getModelID());
return await query.get(); return await query.get();
} }
protected applyRegularConstraints(query: ModelQuery<O>): void {
query.where(this.dbProperties.foreignKey, this.getModelID());
}
public async get(): Promise<O[]> { public async get(): Promise<O[]> {
let models = await super.get(); let models = await super.get();
for (const filter of this.filters) { for (const filter of this.filters) {
@ -161,11 +170,25 @@ export class ManyModelRelation<S extends Model, O extends Model> extends ModelRe
return models; return models;
} }
public async paginate(page: number, perPage: number): Promise<ModelQueryResult<O>> {
if (!this.paginatedCache[perPage]) this.paginatedCache[perPage] = {};
const cache = this.paginatedCache[perPage];
if (!cache[page]) {
const query = this.makeQuery();
this.applyRegularConstraints(query);
cache[page] = await query.paginate(page, perPage);
}
return cache[page];
}
public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> { public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> {
const ids = relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined); const ids = relations.map(r => r.getModelID()).filter(id => id !== null && id !== undefined);
if (ids.length === 0) return []; if (ids.length === 0) return [];
const query = this.getFinalQuery(); const query = this.makeQuery();
query.where(this.dbProperties.foreignKey, ids, WhereTest.IN); query.where(this.dbProperties.foreignKey, ids, WhereTest.IN);
return await query.get(); return await query.get();
} }
@ -175,11 +198,11 @@ export class ManyModelRelation<S extends Model, O extends Model> extends ModelRe
} }
} }
export class ManyThroughModelRelation<S extends Model, O extends Model> extends ModelRelation<S, O, O[]> { export class ManyThroughModelRelation<S extends Model, O extends Model> extends ManyModelRelation<S, O> {
protected readonly dbProperties: PivotRelationDatabaseProperties; protected readonly dbProperties: PivotRelationDatabaseProperties;
constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: PivotRelationDatabaseProperties) { constructor(model: S, foreignFactory: ModelFactory<O>, dbProperties: PivotRelationDatabaseProperties) {
super(model, foreignFactory); super(model, foreignFactory, dbProperties);
this.dbProperties = dbProperties; this.dbProperties = dbProperties;
this.constraint(query => query this.constraint(query => query
.leftJoin(`${this.dbProperties.pivotTable} as pivot`) .leftJoin(`${this.dbProperties.pivotTable} as pivot`)
@ -191,34 +214,27 @@ export class ManyThroughModelRelation<S extends Model, O extends Model> extends
return new ManyThroughModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties); return new ManyThroughModelRelation<S, O>(this.model, this.foreignFactory, this.dbProperties);
} }
public cloneReduceToOne(): OneModelRelation<S, O> {
throw new Error('Cannot reduce many through relation to one model.');
}
public getModelID(): any { public getModelID(): any {
return this.model[this.dbProperties.localKey]; return this.model[this.dbProperties.localKey];
} }
protected async compute(query: ModelQuery<O>): Promise<O[]> { protected async compute(query: ModelQuery<O>): Promise<O[]> {
this.getFinalQuery().where(`pivot.${this.dbProperties.localPivotKey}`, this.getModelID());
return await query.get(); return await query.get();
} }
public async get(): Promise<O[]> { protected applyRegularConstraints(query: ModelQuery<O>): void {
let models = await super.get(); query.where(`pivot.${this.dbProperties.localPivotKey}`, this.getModelID());
for (const filter of this.filters) {
const newModels = [];
for (const model of models) {
if (await filter(model)) {
newModels.push(model);
}
}
models = newModels;
}
return models;
} }
public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> { public async eagerLoad(relations: ModelRelation<S, O, O[]>[]): Promise<ModelQueryResult<O>> {
const ids = relations.map(r => r.getModelID()); const ids = relations.map(r => r.getModelID());
if (ids.length === 0) return []; if (ids.length === 0) return [];
const query = this.getFinalQuery(); const query = this.makeQuery();
query.where(`pivot.${this.dbProperties.localPivotKey}`, ids, WhereTest.IN); query.where(`pivot.${this.dbProperties.localPivotKey}`, ids, WhereTest.IN);
query.pivot(`pivot.${this.dbProperties.localPivotKey}`, `pivot.${this.dbProperties.foreignPivotKey}`); query.pivot(`pivot.${this.dbProperties.localPivotKey}`, `pivot.${this.dbProperties.foreignPivotKey}`);
return await query.get(); return await query.get();