Implement scopes on models and support interacting with them via ModelBuilder
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:
@@ -1,5 +1,5 @@
|
||||
import {ModelKey, QueryRow, QuerySource} from '../types'
|
||||
import {Container, Inject, Instantiable, StaticClass} from '../../di'
|
||||
import {Container, Inject, Instantiable, isInstantiable, StaticClass} from '../../di'
|
||||
import {DatabaseService} from '../DatabaseService'
|
||||
import {ModelBuilder} from './ModelBuilder'
|
||||
import {getFieldsMeta, ModelField} from './Field'
|
||||
@@ -21,6 +21,7 @@ import {Relation, RelationValue} from './relation/Relation'
|
||||
import {HasOne} from './relation/HasOne'
|
||||
import {HasMany} from './relation/HasMany'
|
||||
import {HasOneOrMany} from './relation/HasOneOrMany'
|
||||
import {Scope, ScopeClosure} from './scope/Scope'
|
||||
|
||||
/**
|
||||
* Base for classes that are mapped to tables in a database.
|
||||
@@ -87,6 +88,12 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
*/
|
||||
protected static masks: string[] = []
|
||||
|
||||
/**
|
||||
* Relations that should be eager-loaded by default.
|
||||
* @protected
|
||||
*/
|
||||
protected with: (keyof T)[] = []
|
||||
|
||||
/**
|
||||
* The original row fetched from the database.
|
||||
* @protected
|
||||
@@ -105,6 +112,8 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
*/
|
||||
public relationCache: Collection<{ accessor: string | symbol, relation: Relation<T, any, any> }> = new Collection<{accessor: string | symbol; relation: Relation<T, any, any>}>()
|
||||
|
||||
protected scopes: Collection<{ accessor: string | Instantiable<Scope>, scope: ScopeClosure }> = new Collection<{accessor: string | Instantiable<Scope>; scope: ScopeClosure}>()
|
||||
|
||||
/**
|
||||
* Get the table name for this model.
|
||||
*/
|
||||
@@ -165,6 +174,14 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
builder.field(field.databaseKey)
|
||||
})
|
||||
|
||||
for ( const relation of this.prototype.with ) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
builder.with(relation)
|
||||
}
|
||||
|
||||
builder.withScopes(this.prototype.scopes)
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
@@ -317,6 +334,12 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
builder.field(field.databaseKey)
|
||||
})
|
||||
|
||||
for ( const relation of this.with ) {
|
||||
builder.with(relation)
|
||||
}
|
||||
|
||||
builder.withScopes(this.scopes)
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
@@ -1013,7 +1036,7 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
* @param name
|
||||
* @protected
|
||||
*/
|
||||
protected getRelation<T2 extends Model<T2>>(name: keyof this): Relation<T, T2, RelationValue<T2>> {
|
||||
public getRelation<T2 extends Model<T2>>(name: keyof this): Relation<T, T2, RelationValue<T2>> {
|
||||
const relFn = this[name]
|
||||
|
||||
if ( relFn instanceof Relation ) {
|
||||
@@ -1029,4 +1052,60 @@ export abstract class Model<T extends Model<T>> extends AppClass implements Bus
|
||||
|
||||
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return a Relation.`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a scope on the model.
|
||||
* @param scope
|
||||
* @protected
|
||||
*/
|
||||
protected scope(scope: Instantiable<Scope> | ScopeClosure): this {
|
||||
if ( isInstantiable(scope) ) {
|
||||
if ( !this.hasScope(scope) ) {
|
||||
this.scopes.push({
|
||||
accessor: scope,
|
||||
scope: builder => (this.make<Scope>(scope)).apply(builder),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.scopes.push({
|
||||
accessor: uuid4(),
|
||||
scope,
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a scope on the model with a specific name.
|
||||
* @param name
|
||||
* @param scope
|
||||
* @protected
|
||||
*/
|
||||
protected namedScope(name: string, scope: Instantiable<Scope> | ScopeClosure): this {
|
||||
if ( isInstantiable(scope) ) {
|
||||
if ( !this.hasScope(scope) ) {
|
||||
this.scopes.push({
|
||||
accessor: name,
|
||||
scope: builder => (this.make<Scope>(scope)).apply(builder),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.scopes.push({
|
||||
accessor: name,
|
||||
scope,
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current model has a scope with the given identifier.
|
||||
* @param name
|
||||
* @protected
|
||||
*/
|
||||
protected hasScope(name: string | Instantiable<Scope>): boolean {
|
||||
return Boolean(this.scopes.firstWhere('accessor', '=', name))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user