Skip to content

Commit

Permalink
Add support for relationships in CaseSort
Browse files Browse the repository at this point in the history
  • Loading branch information
luilliarcec committed Oct 8, 2023
1 parent 971e5ca commit 2b69841
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
49 changes: 42 additions & 7 deletions src/Sorts/CaseSort.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Spatie\QueryBuilder\Sorts\Sort;
use TeamQ\QueryBuilder\Concerns\HasPropertyRelationship;
use TeamQ\QueryBuilder\Enums\AggregationType;
use TeamQ\QueryBuilder\Enums\JoinType;

/**
* Add support for sorting using select case, for cases such as Enums, States, Constants,
Expand All @@ -16,26 +19,58 @@
*/
class CaseSort implements Sort
{
/**
* @param array<string|int, string|int> $cases
*/
public function __construct(protected array $cases)
{
use HasPropertyRelationship;

public function __construct(
protected array $cases,
private readonly JoinType $joinType = JoinType::Inner,
private readonly ?AggregationType $aggregationType = null,
) {
}

/**
* {@inheritDoc}
*/
public function __invoke(Builder $query, bool $descending, string $property): void
{
$column = $query->qualifyColumn($property);
if ($this->isRelationProperty($query, $property)) {
[$relationName, $column] = $this->getConstraintParts($property);

$relation = $query;

foreach (explode('.', $relationName) as $partial) {
$relation = $relation->getRelation($partial);
}

$expression = $this->getQueryExpression($relation->qualifyColumn($column));

$query
->orderByPowerJoins(
[$relationName, new Expression($expression)],
$descending ? 'desc' : 'asc',
$this->aggregationType?->value,
$this->joinType->value
);

return;
}

$expression = $this->getQueryExpression($query->qualifyColumn($property));

$query->orderBy(new Expression($expression), $descending ? 'desc' : 'asc');
}

/**
* Generates the SQL expression that will be used to sort.
*/
protected function getQueryExpression(string $column): string
{
$sql = 'case ';
foreach ($this->cases as $key => $value) {
$sql .= "when {$column} = {$key} then '{$value}' ";
}
$sql .= 'end ';

$query->orderBy(new Expression($sql), $descending ? 'desc' : 'asc');
return $sql;
}
}
41 changes: 40 additions & 1 deletion tests/Sorts/CaseSortTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,24 @@
use Spatie\QueryBuilder\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
use TeamQ\QueryBuilder\Sorts\CaseSort;
use Tests\Mocks\Enums\AuthorTypeEnum;
use Tests\Mocks\Enums\BookClassificationEnum;
use Tests\Mocks\Models\Author;
use Tests\Mocks\Models\Book;

beforeEach(function () {
$this->request = new Illuminate\Http\Request();
$this->request->setMethod(Request::METHOD_GET);

$this->firstBook = Book::factory()
->for(
Author::factory()
->create([
'name' => 'Spatie',
'email' => '[email protected]',
'type' => AuthorTypeEnum::VIP,
])
)
->create([
'title' => 'Laravel Beyond Crud',
'isbn' => '758952123',
Expand All @@ -20,6 +30,14 @@
]);

$this->secondBook = Book::factory()
->for(
Author::factory()
->create([
'name' => 'Spatie',
'email' => '[email protected]',
'type' => AuthorTypeEnum::Public,
])
)
->create([
'title' => 'Domain Driven Design for Laravel',
'isbn' => '5895421369',
Expand Down Expand Up @@ -79,5 +97,26 @@
});

it('sort records by relationship fields', function () {
$this->request->query->add([
'sort' => 'author.type',
]);

$cases = collect(AuthorTypeEnum::cases())
->pluck('name', 'value')
->toArray();

$queryBuilder = QueryBuilder::for(Book::class, $this->request)
->allowedSorts([
AllowedSort::custom('author.type', new CaseSort($cases)),
]);

})->skip('WIP');
expect($queryBuilder->toSql())
->toBe(
"select `books`.* from `books` inner join `authors` on `books`.`author_id` = `authors`.`id` order by case when authors.type = 1 then 'VIP' when authors.type = 2 then 'Public' when authors.type = 3 then 'Private' end asc"
)
->and($queryBuilder->get())
->sequence(
fn ($book) => $book->author->type->toBe(AuthorTypeEnum::Public),
fn ($book) => $book->author->type->toBe(AuthorTypeEnum::VIP),
);
});

0 comments on commit 2b69841

Please sign in to comment.