import {Model} from './Model' import {AbstractBuilder} from '../builder/AbstractBuilder' import {AbstractResultIterable} from '../builder/result/AbstractResultIterable' import {Instantiable, StaticClass} from '../../di' import {ModelResultIterable} from './ModelResultIterable' import {Collection} from '../../util' import {ConstraintOperator, ModelKey, ModelKeys} from '../types' import {EscapeValue} from '../dialect/SQLDialect' import {Scope, ScopeClosure} from './scope/Scope' /** * Implementation of the abstract builder whose results yield instances of a given Model, `T`. */ export class ModelBuilder> extends AbstractBuilder { protected eagerLoadRelations: (keyof T)[] = [] protected appliedScopes: Collection<{ accessor: string | Instantiable, scope: ScopeClosure }> = new Collection<{accessor: string | Instantiable; scope: ScopeClosure}>() constructor( /** The model class that is created for results of this query. */ protected readonly ModelClass: StaticClass & Instantiable, ) { super() } public withScopes(scopes: Collection<{ accessor: string | Instantiable, scope: ScopeClosure }>): this { this.appliedScopes = scopes.clone() return this } public getNewInstance(): AbstractBuilder { return this.app().make>(ModelBuilder, this.ModelClass) } public getResultIterable(): AbstractResultIterable { return this.app().make>(ModelResultIterable, this.finalize(), this.registeredConnection, this.ModelClass) } /** * Get a copy of this builder with all of its values finalized. * @override to apply scopes */ public finalize(): AbstractBuilder { const inst = super.finalize() this.appliedScopes.each(rec => rec.scope(inst)) return inst } /** * Apply a WHERE...IN... constraint on the primary key of the model. * @param keys */ public whereKey(keys: ModelKeys): this { return this.whereIn( this.ModelClass.qualifyKey(), this.normalizeModelKeys(keys), ) } /** * Apply a where constraint on the column corresponding the the specified * property on the model. * @param propertyName * @param operator * @param operand */ public whereProperty(propertyName: string, operator: ConstraintOperator, operand?: EscapeValue): this { return this.where( this.ModelClass.propertyToColumn(propertyName), operator, operand, ) } /** * Mark a relation to be eager-loaded. * @param relationName */ public with(relationName: keyof T): this { if ( !this.eagerLoadRelations.includes(relationName) ) { // Try to load the Relation, so we fail if the name is invalid this.make(this.ModelClass).getRelation(relationName) this.eagerLoadRelations.push(relationName) } return this } /** * Prevent a relation from being eager-loaded. * @param relationName */ public without(relationName: keyof T): this { this.eagerLoadRelations = this.eagerLoadRelations.filter(name => name !== relationName) return this } /** * Remove all global scopes from this query. */ public withoutGlobalScopes(): this { this.appliedScopes = new Collection<{accessor: string | Instantiable; scope: ScopeClosure}>() return this } /** * Remove a specific scope from this query by its identifier. * @param name */ public withoutGlobalScope(name: string | Instantiable): this { this.appliedScopes = this.appliedScopes.where('accessor', '=', name) return this } /** Get the list of relations to eager-load. */ public getEagerLoadedRelations(): (keyof T)[] { return [...this.eagerLoadRelations] } /** * Given some format of keys of the model, try to normalize them to a flat array. * @param keys * @protected */ protected normalizeModelKeys(keys: ModelKeys): ModelKey[] { if ( Array.isArray(keys) ) { return keys } else if ( keys instanceof Collection ) { return keys.all() } return [keys] } /** * Create a copy of this builder. * @override to add implementation-specific pass-alongs. */ public clone(): ModelBuilder { const inst = super.clone() as ModelBuilder inst.eagerLoadRelations = [...this.eagerLoadRelations] inst.appliedScopes = this.appliedScopes.clone() return inst } }