Skip to content

Commit 07b92c4

Browse files
committed
fix: search on identically named relation fields
1 parent 52ea890 commit 07b92c4

File tree

7 files changed

+99
-8
lines changed

7 files changed

+99
-8
lines changed

src/Contracts/RelationsResolver.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public function relationForeignKeyFromRelationInstance(Relation $relationInstanc
2323

2424
public function relationLocalKeyFromRelationInstance(Relation $relationInstance): string;
2525

26+
public function getQualifiedRelationFieldName(Relation $relation, string $field): string;
27+
2628
public function guardRelationsForCollection(Collection $entities, array $requestedRelations, ?string $parentRelation = null, bool $normalized = false): Collection;
2729

2830
public function guardRelations(Model $entity, array $requestedRelations, ?string $parentRelation = null, bool $normalized = false);

src/Drivers/Standard/QueryBuilder.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,14 @@ public function applyFiltersToQuery($query, Request $request, array $filterDescr
134134
if ($relation === 'pivot') {
135135
$this->buildPivotFilterQueryWhereClause($relationField, $filterDescriptor, $query, $or);
136136
} else {
137+
$relationInstance = (new $this->resourceModelClass)->{$relation}();
138+
139+
$qualifiedRelationFieldName = $this->relationsResolver->getQualifiedRelationFieldName($relationInstance, $relationField);
140+
137141
$query->{$or ? 'orWhereHas' : 'whereHas'}(
138142
$relation,
139-
function ($relationQuery) use ($relationField, $filterDescriptor) {
140-
$this->buildFilterQueryWhereClause($relationField, $filterDescriptor, $relationQuery);
143+
function ($relationQuery) use ($qualifiedRelationFieldName, $filterDescriptor) {
144+
$this->buildFilterQueryWhereClause($qualifiedRelationFieldName, $filterDescriptor, $relationQuery);
141145
}
142146
);
143147
}
@@ -394,21 +398,25 @@ function ($whereQuery) use ($searchables, $requestedSearchDescriptor) {
394398
$relation = $this->relationsResolver->relationFromParamConstraint($searchable);
395399
$relationField = $this->relationsResolver->relationFieldFromParamConstraint($searchable);
396400

401+
$relationInstance = (new $this->resourceModelClass)->{$relation}();
402+
403+
$qualifiedRelationFieldName = $this->relationsResolver->getQualifiedRelationFieldName($relationInstance, $relationField);
404+
397405
$whereQuery->orWhereHas(
398406
$relation,
399-
function ($relationQuery) use ($relationField, $requestedSearchString, $caseSensitive) {
407+
function ($relationQuery) use ($qualifiedRelationFieldName, $requestedSearchString, $caseSensitive) {
400408
/**
401409
* @var Builder $relationQuery
402410
*/
403411
if (!$caseSensitive) {
404412
return $relationQuery->whereRaw(
405-
"lower({$relationField}) like lower(?)",
413+
"lower({$qualifiedRelationFieldName}) like lower(?)",
406414
['%'.$requestedSearchString.'%']
407415
);
408416
}
409417

410418
return $relationQuery->where(
411-
$relationField,
419+
$qualifiedRelationFieldName,
412420
'like',
413421
'%'.$requestedSearchString.'%'
414422
);
@@ -481,7 +489,9 @@ public function applySortingToQuery($query, Request $request): void
481489
$query->leftJoin($relationTable, $relationForeignKey, '=', $relationLocalKey);
482490
}
483491

484-
$query->orderBy("$relationTable.$relationField", $direction)
492+
$qualifiedRelationFieldName = $this->relationsResolver->getQualifiedRelationFieldName($relationInstance, $relationField);
493+
494+
$query->orderBy($qualifiedRelationFieldName, $direction)
485495
->select($this->getQualifiedFieldName('*'));
486496
} else {
487497
$query->orderBy($this->getQualifiedFieldName($sortableField), $direction);

src/Drivers/Standard/RelationsResolver.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ public function relationForeignKeyFromRelationInstance(Relation $relationInstanc
130130
) : $relationInstance->getQualifiedForeignKey();
131131
}
132132

133+
/**
134+
* Retrieve a fully-qualified field name of the given relation.
135+
*
136+
* @param Relation $relation
137+
* @param string $field
138+
* @return string
139+
*/
140+
public function getQualifiedRelationFieldName(Relation $relation, string $field): string
141+
{
142+
if ($relation instanceof MorphTo) {
143+
return $field;
144+
}
145+
146+
$table = $relation->getModel()->getTable();
147+
148+
return "{$table}.{$field}";
149+
}
150+
133151
/**
134152
* Resolves relation local key from the given relation instance.
135153
*

tests/Feature/StandardIndexFilteringOperationsTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\Facades\Gate;
77
use Orion\Tests\Fixtures\App\Models\Company;
88
use Orion\Tests\Fixtures\App\Models\Post;
9+
use Orion\Tests\Fixtures\App\Models\PostMeta;
910
use Orion\Tests\Fixtures\App\Models\Team;
1011
use Orion\Tests\Fixtures\App\Models\User;
1112
use Orion\Tests\Fixtures\App\Policies\GreenPolicy;
@@ -645,4 +646,60 @@ public function getting_a_list_of_resources_filtered_by_nested_jsonb_array_field
645646
$this->makePaginator([$matchingPost], 'posts/search')
646647
);
647648
}
649+
650+
/** @test */
651+
public function getting_a_list_of_resources_filtered_by_identically_named_relation_fields(): void
652+
{
653+
$user = factory(User::class)->create(['name' => 'John Doe']);
654+
$matchingPost = factory(Post::class)
655+
->create([ 'user_id' => $user->id, ])->fresh();
656+
factory(PostMeta::class)->create(['post_id' => $matchingPost->id, 'name' => 'test']);
657+
658+
factory(Post::class)->create(['publish_at' => Carbon::now()])->fresh();
659+
660+
Gate::policy(Post::class, GreenPolicy::class);
661+
662+
$response = $this->post(
663+
'/api/posts/search',
664+
[
665+
'filters' => [
666+
['field' => 'meta.name', 'operator' => '=', 'value' => 'test'],
667+
['field' => 'user.name', 'operator' => '=', 'value' => 'John Doe'],
668+
],
669+
]
670+
);
671+
672+
$this->assertResourcesPaginated(
673+
$response,
674+
$this->makePaginator([$matchingPost], 'posts/search')
675+
);
676+
}
677+
678+
/** @test */
679+
public function getting_a_list_of_resources_filtered_by_identically_named_fields_on_different_nesting_level(): void
680+
{
681+
$matchingPost = factory(Post::class)
682+
->create(['title' => 'test' ])->fresh();
683+
factory(PostMeta::class)->create(['post_id' => $matchingPost->id, 'title' => 'test']);
684+
685+
686+
factory(Post::class)->create(['publish_at' => Carbon::now()])->fresh();
687+
688+
Gate::policy(Post::class, GreenPolicy::class);
689+
690+
$response = $this->post(
691+
'/api/posts/search',
692+
[
693+
'filters' => [
694+
['field' => 'meta.title', 'operator' => '=', 'value' => 'test'],
695+
['field' => 'title', 'operator' => '=', 'value' => 'test'],
696+
],
697+
]
698+
);
699+
700+
$this->assertResourcesPaginated(
701+
$response,
702+
$this->makePaginator([$matchingPost], 'posts/search')
703+
);
704+
}
648705
}

tests/Fixtures/app/Http/Controllers/PostsController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public function filterableBy(): array
3737
'position',
3838
'publish_at',
3939
'user.name',
40+
'meta.name',
41+
'meta.title',
4042
'meta->nested_field',
4143
'options',
4244
'options->nested_field',
@@ -45,7 +47,7 @@ public function filterableBy(): array
4547

4648
public function searchableBy(): array
4749
{
48-
return ['title', 'user.name'];
50+
return ['title', 'meta.title', 'meta.name', 'user.name'];
4951
}
5052

5153
public function exposedScopes(): array

tests/Fixtures/app/Models/PostMeta.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class PostMeta extends Model
1515
*
1616
* @var array
1717
*/
18-
protected $fillable = ['notes'];
18+
protected $fillable = ['name', 'title', 'notes'];
1919

2020
/**
2121
* @return BelongsTo

tests/Fixtures/database/migrations/2019_03_04_184404_create_post_metas_table.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public function up()
1515
{
1616
Schema::create('post_metas', function (Blueprint $table) {
1717
$table->increments('id');
18+
$table->string('title')->nullable();
19+
$table->string('name')->nullable();
1820
$table->text('notes');
1921
$table->boolean('comments_enabled')->default(true);
2022
$table->unsignedBigInteger('post_id');

0 commit comments

Comments
 (0)