import {Model} from '../Model' import {Relation, RelationValue} from './Relation' import {RelationBuilder} from './RelationBuilder' import {raw} from '../../dialect/SQLDialect' import {AbstractBuilder} from '../../builder/AbstractBuilder' import {ModelBuilder} from '../ModelBuilder' import {Collection, toString} from '../../../util' /** * Base class for 1:1 and 1:M relations. */ export abstract class HasOneOrMany, T2 extends Model, V extends RelationValue> extends Relation { protected constructor( parent: T, related: T2, /** Override the foreign key property. */ protected foreignKeyOverride?: keyof T & string, /** Override the local key property. */ protected localKeyOverride?: keyof T2 & string, ) { super(parent, related) } /** Get the name of the foreign key for this relation. */ public get foreignKey(): string { return this.foreignKeyOverride || this.parent.keyName() } /** Get the name of the local key for this relation. */ public get localKey(): string { return this.localKeyOverride || this.foreignKey } public get foreignColumn(): string { const ctor = this.related.constructor as typeof Model return ctor.propertyToColumn(this.foreignKey) } public get localColumn(): string { const ctor = this.related.constructor as typeof Model return ctor.propertyToColumn(this.localKey) } /** Get the fully-qualified name of the foreign key. */ public get qualifiedForeignKey(): string { return this.related.qualify(this.foreignColumn) } /** Get the fully-qualified name of the local key. */ public get qualifiedLocalKey(): string { return this.related.qualify(this.localColumn) } /** Get the value of the pivot for this relation from the parent model. */ public get parentValue(): any { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return this.parent[this.localKey] } /** Create a new query for this relation. */ public query(): RelationBuilder { return this.builder().select(raw('*')) } /** Apply the relation's constraints on a model query. */ public applyScope(where: AbstractBuilder): void { where.where(subq => { subq.where(this.qualifiedForeignKey, '=', this.parentValue) .whereNotNull(this.qualifiedForeignKey) }) } /** Create an eager-load query matching this relation's models. */ public buildEagerQuery(parentQuery: ModelBuilder, result: Collection): ModelBuilder { const keys = result.pluck(this.localKey as keyof T) .map(toString) .all() return this.related.query() .whereIn(this.foreignColumn, keys) } /** Given a collection of results, filter out those that are relevant to this relation. */ public matchResults(possiblyRelated: Collection): Collection { return possiblyRelated.where(this.foreignColumn as keyof T, '=', this.parentValue) } }