diff --git a/src/Interfaces/LTreeModelInterface.php b/src/Interfaces/LTreeModelInterface.php index 8ca4587..08730ab 100644 --- a/src/Interfaces/LTreeModelInterface.php +++ b/src/Interfaces/LTreeModelInterface.php @@ -32,6 +32,7 @@ public function getLtreeLevel(): int; public function getLtreeProxyDeleteColumns(): array; public function getLtreeProxyUpdateColumns(): array; public function isParentOf(int $id): bool; + public function getAncestorByLevel(int $level = 1); // relations public function ltreeParent(): BelongsTo; diff --git a/src/Traits/LTreeModelTrait.php b/src/Traits/LTreeModelTrait.php index f18d898..e74d643 100644 --- a/src/Traits/LTreeModelTrait.php +++ b/src/Traits/LTreeModelTrait.php @@ -109,4 +109,13 @@ public function renderAsLtree($value, $pad_string = LTreeHelper::PAD_STRING, $pa { return LTreeHelper::renderAsLTree($value, $this->getLtreeLevel(), $pad_string, $pad_type); } + + public function getAncestorByLevel(int $level = 1) + { + return static::whereRaw(sprintf( + "({$this->getLtreePathColumn()} @> text2ltree('%s')) and nlevel(path) = %d", + LTreeHelper::pathAsString($this->getLtreePath()), + $level) + )->first(); + } } diff --git a/tests/Functional/LtreeTest.php b/tests/Functional/LtreeTest.php index 60fee2f..69369ef 100644 --- a/tests/Functional/LtreeTest.php +++ b/tests/Functional/LtreeTest.php @@ -161,6 +161,21 @@ public function root(): void } } + /** @test */ + public function getAncestorByLevel(): void + { + $tree = $this->createTreeNodes($this->getTreeNodes()); + $parent = $tree[1]; + $descendants = $parent::descendantsOf($parent)->withoutSelf(1); + $this->assertGreaterThan(0, $descendants->count()); + $descendants->each(static function ($descendant) use ($parent) { + $descendant->getAncestorByLevel($parent->id); + }); + + $this->assertSame($tree[5]->getAncestorByLevel(2)->id, $tree[2]->id); + $this->assertSame($tree[8]->getAncestorByLevel(3)->id, $tree[6]->id); + } + private function initLTreeService() { DB::statement('CREATE EXTENSION IF NOT EXISTS LTREE');