Skip to content

Commit fe628f5

Browse files
authored
Sorting by belongs to. (#319)
* Sorting by belongs to. * Apply fixes from StyleCI (#318) * Clean up.
1 parent 6907c1e commit fe628f5

File tree

12 files changed

+449
-120
lines changed

12 files changed

+449
-120
lines changed

docs/docs/4.0/filtering/filtering.md

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Laravel Restify provides configurable and powerful way of filtering over entitie
44

55
## Search
66

7-
If you want search for some specific fields from a model, you have to define these fields in the `$search` static
7+
If you want search for some specific fields from a model, you have to define these fields in the `$search` static
88
property:
99

1010
```php
@@ -13,24 +13,23 @@ class PostRepository extends Repository
1313
public static $search = ['id', 'title'];
1414
```
1515

16-
Now `posts` are searchable by `id` and `title`, so you could use `search` query param for filtering the index
17-
request:
16+
Now `posts` are searchable by `id` and `title`, so you could use `search` query param for filtering the index request:
1817

1918
```http request
2019
GET: /api/restify/posts?search="Test title"
2120
```
2221

2322
### Get available searchables
2423

25-
You can use the following request to available searchable attributes for a repository:
24+
You can use the following request to available searchable attributes for a repository:
2625

2726
```http request
2827
/api/restify/posts/filters?only=searchables
2928
```
3029

3130
## Match
3231

33-
Matching by specific attributes may be useful if you want an exact matching.
32+
Matching by specific attributes may be useful if you want an exact matching.
3433

3534
Repository configuration:
3635

@@ -44,7 +43,7 @@ class PostRepository extends Repository
4443
}
4544
```
4645

47-
As we may notice the match configuration is an associative array, defining the attribute name and type mapping.
46+
As we may notice the match configuration is an associative array, defining the attribute name and type mapping.
4847

4948
Available types:
5049

@@ -68,7 +67,7 @@ GET: /api/restify/posts?title="Some title"
6867

6968
### Match datetime
7069

71-
The `datetime` filter add behind the scene an `whereDate` query.
70+
The `datetime` filter add behind the scene an `whereDate` query.
7271

7372
```php
7473
class PostRepository extends Repository
@@ -79,7 +78,7 @@ class PostRepository extends Repository
7978
}
8079
```
8180

82-
Request:
81+
Request:
8382

8483
```http request
8584
GET: /api/restify/posts?published_at=2020-12-01
@@ -106,7 +105,7 @@ class PostRepository extends Repository
106105
}
107106
```
108107

109-
Request:
108+
Request:
110109

111110
```http request
112111
GET: /api/restify/posts?id=1,2,3
@@ -128,7 +127,7 @@ GET: /api/restify/posts?-id=1,2,3
128127

129128
This will return all posts where doesn't have the `id` in the `[1,2,3]` list.
130129

131-
You can apply `-` (negation) for every match:
130+
You can apply `-` (negation) for every match:
132131

133132
```http request
134133
GET: /api/restify/posts?-title="Some title"
@@ -138,7 +137,9 @@ This will return all posts that doesn't contain `Some title` substring.
138137

139138
### Match closure
140139

141-
There may be situations when the filter you want to apply not necessarily is a database attributes. In your `booted` method you can add more filters for the `$match` where the key represents the field used as query param, and value should be a `Closure` which gets the request and current query `Builder`:
140+
There may be situations when the filter you want to apply not necessarily is a database attributes. In your `booted`
141+
method you can add more filters for the `$match` where the key represents the field used as query param, and value
142+
should be a `Closure` which gets the request and current query `Builder`:
142143

143144
```php
144145
// UserRepository
@@ -154,16 +155,18 @@ protected static function booted()
154155
}
155156
```
156157

157-
So now you can query this:
158+
So now you can query this:
158159

159160
```http request
160161
GET: /api/restify/users?active=true
161162
```
162163

163-
164164
### Matchable
165165

166-
Sometimes you may have a large logic into a match `Closure`, and the booted method could become a mess. To prevent this, `Restify` provides a declarative way to define `matchers`. For this purpose you should define a class, and implement the `Binaryk\LaravelRestify\Repositories\Matchable` contract. You can use the following command to generate that:
166+
Sometimes you may have a large logic into a match `Closure`, and the booted method could become a mess. To prevent
167+
this, `Restify` provides a declarative way to define `matchers`. For this purpose you should define a class, and
168+
implement the `Binaryk\LaravelRestify\Repositories\Matchable` contract. You can use the following command to generate
169+
that:
167170

168171
```shell script
169172
php artisan restify:match ActiveMatch
@@ -203,26 +206,26 @@ You can use the following request to get all repository matches:
203206
/api/restify/posts/filters?only=matches
204207
```
205208

206-
## Sort
207-
When index query entities, usually we have to sort by specific attributes.
208-
This requires the `$sort` configuration:
209+
## Sort
210+
211+
When index query entities, usually we have to sort by specific attributes. This requires the `$sort` configuration:
209212

210213
```php
211214
class PostRepository extends Repository
212215
{
213216
public static $sort = ['id'];
214217
```
215-
216-
Performing request requires the sort query param:
217-
218-
Sorting DESC requires a minus (`-`) sign before the attribute name:
219-
218+
219+
Performing request requires the sort query param:
220+
221+
Sorting DESC requires a minus (`-`) sign before the attribute name:
222+
220223
```http request
221224
GET: /api/restify/posts?sort=-id
222225
```
223226

224-
Sorting ASC:
225-
227+
Sorting ASC:
228+
226229
```http request
227230
GET: /api/restify/posts?sort=id
228231
```
@@ -233,22 +236,58 @@ or with plus sign before the field:
233236
GET: /api/restify/posts?sort=+id
234237
```
235238

239+
### Sort using BelongsTo
240+
241+
Sometimes you may need to sort by a `belongsTo` relationship. This become a breeze with Restify. Firstly you have to
242+
instruct your sort to use a relationship:
243+
244+
```php
245+
// PostRepository
246+
use Binaryk\LaravelRestify\Fields\BelongsTo;
247+
use Binaryk\LaravelRestify\Filters\SortableFilter;
248+
249+
public static function sorts(): array
250+
{
251+
return [
252+
'users.name' => SortableFilter::make()
253+
->setColumn('users.name')
254+
->usingBelongsTo(
255+
BelongsTo::make('user', 'user', UserRepository::class),
256+
)
257+
];
258+
}
259+
```
260+
261+
Make sure that the column is fully qualified (include the table name).
262+
263+
The request could look like:
264+
265+
```http request
266+
GET: /api/restify/posts?sort=-users.name
267+
```
268+
269+
This will return all posts, sorted descending by users name.
270+
271+
:::tip Set column optional
272+
As you may notice we have typed twice the `users.name` (on the array key, and as argument in the `setColumn` method). As soon as you use the fully qualified key name, you can avoid the `setColumn` call, since the column will be injected automatically based on the `sorts` key.
273+
:::
274+
236275
### Get available sorts
237276

238-
You can use the following request to get sortable attributes for a repository:
277+
You can use the following request to get sortable attributes for a repository:
239278

240279
```http request
241280
/api/restify/posts/filters?only=sortables
242281
```
243282

244-
:::tip All filters
245-
You can use `/api/restify/posts/filters?only=sortables` request, and concatenate: `?only=sortables,matches, searchables` to get all of them at once.
283+
:::tip All filters You can use `/api/restify/posts/filters?only=sortables` request, and
284+
concatenate: `?only=sortables,matches, searchables` to get all of them at once.
246285
:::
247286

248287
## Eager loading - aka withs
249288

250-
When get a repository index or details about a single entity, often we have to get the related entities (we have access to).
251-
This eager loading is configurable by Restify as following:
289+
When get a repository index or details about a single entity, often we have to get the related entities (we have access
290+
to). This eager loading is configurable by Restify as following:
252291

253292
```php
254293
public static $related = ['posts'];
@@ -262,16 +301,16 @@ GET: /api/restify/users?related=posts
262301

263302
## Custom data
264303

265-
You are not limited to add only relations under the `related` array. You can use whatever you want, for instance you can return a simple model, or a collection. Basically any serializable data could be added there. For example:
266-
304+
You are not limited to add only relations under the `related` array. You can use whatever you want, for instance you can
305+
return a simple model, or a collection. Basically any serializable data could be added there. For example:
267306

268307
```php
269308
public static $related = [
270309
'foo'
271310
];
272311
```
273312

274-
Then in the `Post` model we can define this method as:
313+
Then in the `Post` model we can define this method as:
275314

276315
```php
277316
public function foo() {
@@ -281,7 +320,8 @@ public function foo() {
281320

282321
### Custom data format
283322

284-
You can use a custom related cast class (aka transformer). You can do so by modifying the `restify.casts.related` property. The default related cast is `Binaryk\LaravelRestify\Repositories\Casts\RelatedCast`.
323+
You can use a custom related cast class (aka transformer). You can do so by modifying the `restify.casts.related`
324+
property. The default related cast is `Binaryk\LaravelRestify\Repositories\Casts\RelatedCast`.
285325

286326
The cast class should extends the `Binaryk\LaravelRestify\Repositories\Casts\RepositoryCast` abstract class.
287327

src/Eager/RelatedCollection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Binaryk\LaravelRestify\Eager;
44

5+
use Binaryk\LaravelRestify\Fields\BelongsTo;
56
use Binaryk\LaravelRestify\Fields\EagerField;
67
use Binaryk\LaravelRestify\Fields\Field;
8+
use Binaryk\LaravelRestify\Filters\SortableFilter;
79
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
810
use Illuminate\Support\Collection;
911

@@ -25,6 +27,14 @@ public function forEager(RestifyRequest $request): self
2527
->unique('attribute');
2628
}
2729

30+
public function mapIntoSortable(RestifyRequest $request): self
31+
{
32+
return $this->filter(fn (EagerField $field) => $field->isSortable())
33+
//Now we support only belongs to sort from related.
34+
->filter(fn (EagerField $field) => $field instanceof BelongsTo)
35+
->map(fn (BelongsTo $field) => SortableFilter::make()->usingBelongsTo($field));
36+
}
37+
2838
public function inRequest(RestifyRequest $request): self
2939
{
3040
return $this

src/Fields/EagerField.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Binaryk\LaravelRestify\Repositories\Repository;
66
use Illuminate\Auth\Access\AuthorizationException;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Database\Eloquent\Relations\Relation;
79
use Illuminate\Http\Request;
810
use Illuminate\Support\Facades\Gate;
911

@@ -64,4 +66,27 @@ public function resolve($repository, $attribute = null)
6466

6567
return $this;
6668
}
69+
70+
public function getRelation(Repository $repository): Relation
71+
{
72+
return $repository->resource->newQuery()
73+
->getRelation($this->relation);
74+
}
75+
76+
public function getRelatedModel(Repository $repository): ?Model
77+
{
78+
return $this->getRelation($repository)->getRelated();
79+
}
80+
81+
public function getRelatedKey(Repository $repository): string
82+
{
83+
return $repository->resource->qualifyColumn(
84+
$this->getRelation($repository)->getRelated()->getForeignKey()
85+
);
86+
}
87+
88+
public function getQualifiedKey(Repository $repository): string
89+
{
90+
return $this->getRelation($repository)->getRelated()->getQualifiedKeyName();
91+
}
6792
}

src/Fields/Field.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ class Field extends OrganicField implements JsonSerializable
120120

121121
public $label;
122122

123+
/**
124+
* Indicates if the field should be sortable.
125+
*
126+
* @var bool
127+
*/
128+
public $sortable = false;
129+
123130
/**
124131
* Create a new field.
125132
*
@@ -395,7 +402,43 @@ public function getUpdatingBulkRules(): array
395402
}
396403

397404
/**
398-
* Resolve the field's value for display.
405+
* Determine if the attribute is computed.
406+
*
407+
* @return bool
408+
*/
409+
public function computed()
410+
{
411+
return (is_callable($this->attribute) && ! is_string($this->attribute)) ||
412+
is_callable($this->computedCallback) || $this->attribute == 'Computed';
413+
}
414+
415+
/**
416+
* Specify that this attribute should be sortable.
417+
*
418+
* @param bool $value
419+
* @return $this
420+
*/
421+
public function sortable($value = true)
422+
{
423+
if (! $this->computed()) {
424+
$this->sortable = $value;
425+
}
426+
427+
return $this;
428+
}
429+
430+
/**
431+
* Indicates if this attribute is sortable.
432+
*
433+
* @return bool
434+
*/
435+
public function isSortable()
436+
{
437+
return $this->sortable;
438+
}
439+
440+
/**
441+
* Resolve the attribute's value for display.
399442
*
400443
* @param mixed $repository
401444
* @param string|null $attribute

0 commit comments

Comments
 (0)