diff --git a/src/query/Query.ts b/src/query/Query.ts index 18d6d98..c1a4965 100644 --- a/src/query/Query.ts +++ b/src/query/Query.ts @@ -193,6 +193,18 @@ export class Query { return this } + /** + * Set to eager load the passed relationship recursively. + * Should be a parent / child relationship within a single model. + */ + withRecursive(name: string, depth: number = 3): Query { + this.with(name, (query) => { + depth > 0 && query.withRecursive(name, depth - 1) + }) + + return this + } + /** * Set to eager load all top-level relationships. Constraint is set for all relationships. */ diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index 87e1e57..725e701 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -147,6 +147,14 @@ export class Repository { return this.query().with(name, callback) } + /** + * Set the relationship that should be eager loaded recursively. + * Should be a parent / child relationship within a single model. + */ + withRecursive(name: string, depth?: number): Query { + return this.query().withRecursive(name, depth) + } + /** * Set to eager load all top-level relationships. Constraint is set for all relationships. */ diff --git a/test/feature/relations/eager_loads/eager_loads_recursive.spec.ts b/test/feature/relations/eager_loads/eager_loads_recursive.spec.ts index d40c675..2ede1a0 100644 --- a/test/feature/relations/eager_loads/eager_loads_recursive.spec.ts +++ b/test/feature/relations/eager_loads/eager_loads_recursive.spec.ts @@ -1,5 +1,5 @@ import { assertModel, createStore, fillState } from 'test/Helpers' -import { Model, Attr, Str, BelongsTo, HasOne } from '@/index' +import { Model, Attr, Str, BelongsTo, HasOne, Num, HasMany } from '@/index' describe('feature/relations/eager_loads_recursive', () => { class User extends Model { @@ -23,6 +23,17 @@ describe('feature/relations/eager_loads_recursive', () => { user!: User } + class Node extends Model { + static entity = 'nodes' + + @Attr() id!: number + @Str('') name!: string + + @Num(null, { nullable: true }) parentId!: number | null + @HasMany(() => Node, 'parentId') children!: Node[] + @BelongsTo(() => Node, 'parentId') parent!: Node + } + it('eager loads all relations recursively', () => { const store = createStore() @@ -66,4 +77,109 @@ describe('feature/relations/eager_loads_recursive', () => { name: 'John Doe' }) }) + + it('eager loads all parents recursively', () => { + const store = createStore() + + fillState(store, { + nodes: { + 1: { id: 1, name: 'Root', parentId: null }, + 2: { id: 2, name: 'Root Child 1', parentId: 1 }, + 3: { id: 3, name: 'Root Child 2', parentId: 1 }, + 4: { id: 4, name: 'Grandchild', parentId: 3 } + } + }) + + const user = store.$repo(Node).withRecursive('parent').find(4)! + + expect(user.parent).toBeInstanceOf(Node) + expect(user.parent.parent).toBeInstanceOf(Node) + assertModel(user.parent.parent, { + id: 1, + name: 'Root', + parentId: null, + parent: null, + children: undefined + }) + }) + + it('eager loads all parents recursively with limit', () => { + const store = createStore() + + fillState(store, { + nodes: { + 1: { id: 1, name: 'Root', parentId: null }, + 2: { id: 2, name: 'Root Child 1', parentId: 1 }, + 3: { id: 3, name: 'Root Child 2', parentId: 1 }, + 4: { id: 4, name: 'Grandchild', parentId: 3 }, + 5: { id: 5, name: 'Great Grandchild', parentId: 4 } + } + }) + + const user = store.$repo(Node).withRecursive('parent', 1).find(5)! + + expect(user.parent).toBeInstanceOf(Node) + expect(user.parent.parent).toBeInstanceOf(Node) + expect(user.parent.parent.parent).toBeUndefined() + assertModel(user.parent.parent, { + id: 3, + name: 'Root Child 2', + parentId: 1, + parent: undefined, + children: undefined + }) + }) + + it('eager loads all children recursively', () => { + const store = createStore() + + fillState(store, { + nodes: { + 1: { id: 1, name: 'Root', parentId: null }, + 2: { id: 2, name: 'Root Child 1', parentId: 1 }, + 3: { id: 3, name: 'Root Child 2', parentId: 1 }, + 4: { id: 4, name: 'Grandchild', parentId: 3 } + } + }) + + const user = store.$repo(Node).withRecursive('children').find(1)! + + expect(user.children).toHaveLength(2) + expect(user.children[1].children).toHaveLength(1) + expect(user.children[1].children[0]).toBeInstanceOf(Node) + assertModel(user.children[1].children[0], { + id: 4, + name: 'Grandchild', + parentId: 3, + parent: undefined, + children: [] + }) + }) + + it('eager loads all children recursively with limit', () => { + const store = createStore() + + fillState(store, { + nodes: { + 1: { id: 1, name: 'Root', parentId: null }, + 2: { id: 2, name: 'Root Child 1', parentId: 1 }, + 3: { id: 3, name: 'Root Child 2', parentId: 1 }, + 4: { id: 4, name: 'Grandchild', parentId: 3 }, + 5: { id: 5, name: 'Great Grandchild', parentId: 4 } + } + }) + + const user = store.$repo(Node).withRecursive('children', 1).find(1)! + + expect(user.children).toHaveLength(2) + expect(user.children[1].children).toHaveLength(1) + expect(user.children[1].children[0]).toBeInstanceOf(Node) + assertModel(user.children[1].children[0], { + id: 4, + name: 'Grandchild', + parentId: 3, + parent: undefined, + children: undefined + }) + }) })