From 843e13f877c08ce546e6951add30afde3b72d8f0 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Wed, 16 Dec 2020 17:04:06 +0200 Subject: [PATCH] Filter definition. (#310) * Return repository key on filters. * Apply fixes from StyleCI (#307) * Adding filter definition. * Apply fixes from StyleCI (#308) * docs * Map into qualified classes. * Apply fixes from StyleCI (#309) * wip --- docs/docs/4.0/search/search.md | 32 ++++ src/Commands/FilterCommand.php | 65 +++++++++ src/Commands/stubs/filter.stub | 14 ++ src/Filter.php | 76 +++++++++- src/Filters/FilterDefinition.php | 49 +++++++ src/Filters/MatchFilter.php | 91 ++++++++---- src/Filters/SearchableFilter.php | 30 +--- src/Filters/SortableFilter.php | 34 +---- .../RepositoryFilterController.php | 16 +- src/LaravelRestifyServiceProvider.php | 2 + src/Repositories/Repository.php | 2 +- .../Search/RepositorySearchService.php | 138 ++++++++---------- src/Traits/InteractWithSearch.php | 43 ++++++ .../Actions/PerformActionsControllerTest.php | 4 +- .../Feature/Filters/FilterDefinitionTest.php | 37 +++++ tests/Feature/RepositorySearchServiceTest.php | 61 ++++++++ 16 files changed, 509 insertions(+), 185 deletions(-) create mode 100644 src/Commands/FilterCommand.php create mode 100644 src/Commands/stubs/filter.stub create mode 100644 src/Filters/FilterDefinition.php create mode 100644 tests/Feature/Filters/FilterDefinitionTest.php diff --git a/docs/docs/4.0/search/search.md b/docs/docs/4.0/search/search.md index 6e6787da..c5f7e6cd 100644 --- a/docs/docs/4.0/search/search.md +++ b/docs/docs/4.0/search/search.md @@ -154,6 +154,38 @@ So now you can query this: GET: /api/restify/users?is_active=true ``` +### Match definition + +You can implement a match filter definition, and specify for example related repository: + +```php + public static $match = [ + 'title' => 'string', + 'user_id' => MatchFilter::make() + ->setType('int') + ->setRelatedRepositoryKey(UserRepository::uriKey()), +]; +``` +When you will list this filter (with `posts/filters?only=matches`), you will get: + +```json + { + "class": "Binaryk\LaravelRestify\Filters\MatchFilter" + "key": "matches" + "type": "string" + "column": "title" + "options": [] + }, + { + "class": "Binaryk\LaravelRestify\Filters\MatchFilter" + "key": "matches" + "type": "int" + "column": "user_id" + "options": [] + "related_repository_key": "users" + "related_repository_url": "//users" + } +``` ## Sort When index query entities, usually we have to sort by specific attributes. diff --git a/src/Commands/FilterCommand.php b/src/Commands/FilterCommand.php new file mode 100644 index 00000000..de39f7af --- /dev/null +++ b/src/Commands/FilterCommand.php @@ -0,0 +1,65 @@ +option('force')) { + return false; + } + } + + /** + * Build the class with the given name. + * This method should return the file class content. + * + * @param string $name + * @return string + * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + */ + protected function buildClass($name) + { + if (false === Str::endsWith($name, 'Filter')) { + $name .= 'Filter'; + } + + return tap(parent::buildClass($name), function ($stub) use ($name) { + return str_replace(['{{ attribute }}', '{{ query }}'], Str::snake( + Str::beforeLast(class_basename($name), 'Filter') + ), $stub); + }); + } + + protected function getStub() + { + return __DIR__.'/stubs/filter.stub'; + } + + protected function getPath($name) + { + if (false === Str::endsWith($name, 'Filter')) { + $name .= 'Filter'; + } + + return parent::getPath($name); + } + + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Restify\Matchers'; + } +} diff --git a/src/Commands/stubs/filter.stub b/src/Commands/stubs/filter.stub new file mode 100644 index 00000000..9342fe0e --- /dev/null +++ b/src/Commands/stubs/filter.stub @@ -0,0 +1,14 @@ +where('{{ attribute }}', $request->input('{{ query }}')); + } +} diff --git a/src/Filter.php b/src/Filter.php index c18b7c08..bf69baef 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -3,6 +3,7 @@ namespace Binaryk\LaravelRestify; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Binaryk\LaravelRestify\Repositories\Repository; use Binaryk\LaravelRestify\Traits\Make; use Closure; use Illuminate\Http\Request; @@ -15,12 +16,18 @@ abstract class Filter implements JsonSerializable public $type = 'value'; + public $column; + public $value; public $canSeeCallback; public static $uriKey; + public $relatedRepositoryKey; + + public Repository $repository; + public function __construct() { $this->booted(); @@ -55,6 +62,25 @@ protected function getType() return $this->type; } + public function getColumn(): ?string + { + return $this->column; + } + + public function setColumn(string $column): self + { + $this->column = $column; + + return $this; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + public function options(Request $request) { // noop @@ -76,6 +102,36 @@ public function resolve(RestifyRequest $request, $filter) $this->value = $filter; } + public function getRelatedRepositoryKey(): ?string + { + return $this->relatedRepositoryKey; + } + + public function setRelatedRepositoryKey(string $repositoryKey): self + { + $this->relatedRepositoryKey = $repositoryKey; + + return $this; + } + + public function setRepository(Repository $repository): self + { + $this->repository = $repository; + + return $this; + } + + public function getRelatedRepositoryUrl(): ?string + { + return ($key = $this->getRelatedRepositoryKey()) + ? with(Restify::repositoryForKey($key), function ($repository = null) { + if (is_subclass_of($repository, Repository::class)) { + return Restify::path($repository::uriKey()); + } + }) + : null; + } + /** * Get the URI key for the filter. * @@ -87,24 +143,28 @@ public static function uriKey() return static::$uriKey; } - $kebabWithoutRepository = Str::kebab(Str::replaceLast('Filter', '', class_basename(get_called_class()))); + $kebabWithoutFilter = Str::kebab(Str::replaceLast('Filter', '', class_basename(get_called_class()))); - /** - * e.g. UserRepository => users - * e.g. LaravelEntityRepository => laravel-entities. - */ - return Str::plural($kebabWithoutRepository); + return Str::plural($kebabWithoutFilter); } public function jsonSerialize() { - return [ + return with([ 'class' => static::class, 'key' => static::uriKey(), 'type' => $this->getType(), + 'column' => $this->getColumn(), 'options' => collect($this->options(app(Request::class)))->map(function ($value, $key) { return is_array($value) ? ($value + ['property' => $key]) : ['label' => $key, 'property' => $value]; })->values()->all(), - ]; + ], function (array $initial) { + return $this->relatedRepositoryKey + ? array_merge($initial, [ + 'related_repository_key' => $this->getRelatedRepositoryKey(), + 'related_repository_url' => $this->getRelatedRepositoryUrl(), + ]) + : $initial; + }); } } diff --git a/src/Filters/FilterDefinition.php b/src/Filters/FilterDefinition.php new file mode 100644 index 00000000..23b5ec04 --- /dev/null +++ b/src/Filters/FilterDefinition.php @@ -0,0 +1,49 @@ +getRelatedRepositoryKey()) + ? with(Restify::repositoryForKey($key), function ($repository = null) { + if (is_subclass_of($repository, Repository::class)) { + return Restify::path($repository::uriKey()); + } + }) + : null; + } + + public function jsonSerialize() + { + return with([ + 'type' => $this->getType(), + ], function (array $initial) { + return static::$relatedRepositoryKey + ? array_merge($initial, [ + 'related_repository_key' => $this->getRelatedRepositoryKey(), + 'related_repository_url' => $this->getRelatedRepositoryUrl(), + ]) + : $initial; + }); + } +} diff --git a/src/Filters/MatchFilter.php b/src/Filters/MatchFilter.php index 4335e059..ee8c8423 100644 --- a/src/Filters/MatchFilter.php +++ b/src/Filters/MatchFilter.php @@ -2,45 +2,80 @@ namespace Binaryk\LaravelRestify\Filters; +use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Filter; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Repository; -use Illuminate\Support\Collection; +use Illuminate\Support\Str; class MatchFilter extends Filter { - public $column = 'id'; - public static $uriKey = 'matches'; public function filter(RestifyRequest $request, $query, $value) { - //@todo improve this - $query->where($this->column, $value); - } + $key = Str::afterLast($this->column, '.'); + $negation = false; - public static function makeFromSimple($column, $type): self - { - return tap(new static, function (MatchFilter $filter) use ($column, $type) { - $filter->type = $type; - $filter->column = $column; - }); - } + if ($request->has('-'.$key)) { + $negation = true; + } - public static function makeForRepository(Repository $repository): Collection - { - return collect($repository::getMatchByFields())->map(function ($type, $column) { - return static::makeFromSimple($column, $type); - })->values(); - } + if (empty($value)) { + return $query; + } - public function jsonSerialize() - { - return [ - 'class' => static::class, - 'type' => $this->getType(), - 'key' => static::uriKey(), - 'column' => $this->column, - ]; + $match = $value; + + if ($negation) { + $key = Str::after($key, '-'); + } + + $field = $this->column; + + if ($match === 'null') { + if ($negation) { + $query->whereNotNull($field); + } else { + $query->whereNull($field); + } + } else { + switch ($this->getType()) { + case RestifySearchable::MATCH_TEXT: + case 'string': + $query->where($field, $negation ? '!=' : '=', $match); + break; + case RestifySearchable::MATCH_BOOL: + case 'boolean': + if ($match === 'false') { + $query->where(function ($query) use ($field, $negation) { + if ($negation) { + return $query->where($field, true); + } else { + return $query->where($field, '=', false)->orWhereNull($field); + } + }); + break; + } + $query->where($field, $negation ? '!=' : '=', true); + break; + case RestifySearchable::MATCH_INTEGER: + case 'number': + case 'int': + $query->where($field, $negation ? '!=' : '=', (int) $match); + break; + case RestifySearchable::MATCH_DATETIME: + $query->whereDate($field, $negation ? '!=' : '=', $match); + break; + case RestifySearchable::MATCH_ARRAY: + $match = explode(',', $match); + + if ($negation) { + $query->whereNotIn($field, $match); + } else { + $query->whereIn($field, $match); + } + break; + } + } } } diff --git a/src/Filters/SearchableFilter.php b/src/Filters/SearchableFilter.php index 753cd5bb..10be1ed7 100644 --- a/src/Filters/SearchableFilter.php +++ b/src/Filters/SearchableFilter.php @@ -4,41 +4,17 @@ use Binaryk\LaravelRestify\Filter; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Repository; -use Illuminate\Support\Collection; class SearchableFilter extends Filter { - public $column = 'id'; - public static $uriKey = 'searchables'; public function filter(RestifyRequest $request, $query, $value) { - //@todo improve this - $query->where($this->column, 'LIKE', "%{$value}%"); - } - - public static function makeFromSimple($column): self - { - return tap(new static, function (SearchableFilter $filter) use ($column) { - $filter->column = $column; - }); - } + $connectionType = $this->repository->model()->getConnection()->getDriverName(); - public static function makeForRepository(Repository $repository): Collection - { - return collect($repository::getSearchableFields())->map(function ($column) { - return static::makeFromSimple($column); - }); - } + $likeOperator = $connectionType == 'pgsql' ? 'ilike' : 'like'; - public function jsonSerialize() - { - return [ - 'class' => static::class, - 'key' => static::uriKey(), - 'column' => $this->column, - ]; + $query->orWhere($this->column, $likeOperator, '%'.$value.'%'); } } diff --git a/src/Filters/SortableFilter.php b/src/Filters/SortableFilter.php index aead4026..3ec1316e 100644 --- a/src/Filters/SortableFilter.php +++ b/src/Filters/SortableFilter.php @@ -4,41 +4,17 @@ use Binaryk\LaravelRestify\Filter; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Repository; -use Illuminate\Support\Collection; class SortableFilter extends Filter { - public $column = 'id'; - public static $uriKey = 'sortables'; public function filter(RestifyRequest $request, $query, $direction) { - //@todo improve this - $query->orderBy($this->column, $direction); - } - - public static function makeFromSimple($column): self - { - return tap(new static, function (SortableFilter $filter) use ($column) { - $filter->column = $column; - }); - } - - public static function makeForRepository(Repository $repository): Collection - { - return collect($repository::getOrderByFields())->map(function ($column) { - return static::makeFromSimple($column); - }); - } - - public function jsonSerialize() - { - return [ - 'class' => static::class, - 'key' => static::uriKey(), - 'column' => $this->column, - ]; + $query->orderBy($this->column, + $direction === '-' + ? 'desc' + : 'asc' + ); } } diff --git a/src/Http/Controllers/RepositoryFilterController.php b/src/Http/Controllers/RepositoryFilterController.php index 2bf76e61..12d76f82 100644 --- a/src/Http/Controllers/RepositoryFilterController.php +++ b/src/Http/Controllers/RepositoryFilterController.php @@ -2,9 +2,6 @@ namespace Binaryk\LaravelRestify\Http\Controllers; -use Binaryk\LaravelRestify\Filters\MatchFilter; -use Binaryk\LaravelRestify\Filters\SearchableFilter; -use Binaryk\LaravelRestify\Filters\SortableFilter; use Binaryk\LaravelRestify\Http\Requests\RepositoryFiltersRequest; use Illuminate\Support\Collection; @@ -16,22 +13,13 @@ public function __invoke(RepositoryFiltersRequest $request) return $this->response()->data( $repository->availableFilters($request) - // After ->when($request->has('include'), function (Collection $collection) use ($repository, $request) { return $collection->merge( - collect(str_getcsv($request->input('include')))->map(fn ($key) => collect([ - SearchableFilter::uriKey() => SearchableFilter::class, - MatchFilter::uriKey() => MatchFilter::class, - SortableFilter::uriKey() => SortableFilter::class, - ])->get($key))->flatMap(fn ($filterable) => $filterable::makeForRepository($repository)) + collect(str_getcsv($request->input('include')))->flatMap(fn ($key) => $repository::collectFilters($key)) ); }) ->when($request->has('only'), function (Collection $collection) use ($repository, $request) { - return collect(str_getcsv($request->input('only')))->map(fn ($key) => collect([ - SearchableFilter::uriKey() => SearchableFilter::class, - MatchFilter::uriKey() => MatchFilter::class, - SortableFilter::uriKey() => SortableFilter::class, - ])->get($key))->flatMap(fn ($filterable) => $filterable::makeForRepository($repository)); + return collect(str_getcsv($request->input('only')))->flatMap(fn ($key) => $repository::collectFilters($key)); }) ); } diff --git a/src/LaravelRestifyServiceProvider.php b/src/LaravelRestifyServiceProvider.php index ea690770..bc64ee43 100644 --- a/src/LaravelRestifyServiceProvider.php +++ b/src/LaravelRestifyServiceProvider.php @@ -6,6 +6,7 @@ use Binaryk\LaravelRestify\Commands\BaseRepositoryCommand; use Binaryk\LaravelRestify\Commands\CheckPassport; use Binaryk\LaravelRestify\Commands\DevCommand; +use Binaryk\LaravelRestify\Commands\FilterCommand; use Binaryk\LaravelRestify\Commands\MatcherCommand; use Binaryk\LaravelRestify\Commands\PolicyCommand; use Binaryk\LaravelRestify\Commands\Refresh; @@ -63,6 +64,7 @@ public function register() RepositoryCommand::class, ActionCommand::class, MatcherCommand::class, + FilterCommand::class, DevCommand::class, ]); } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index ec5754bb..54431ced 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -901,7 +901,7 @@ public function response($content = '', $status = 200, array $headers = []): Res public function serializeForShow(RestifyRequest $request): array { return $this->filter([ - 'id' => $this->when($this->resource->id, fn () => $this->getId($request)), + 'id' => $this->when(optional($this->resource)->id, fn () => $this->getId($request)), 'type' => $this->when($type = $this->getType($request), $type), 'attributes' => $request->isShowRequest() ? $this->resolveShowAttributes($request) : $this->resolveIndexAttributes($request), 'relationships' => $this->when(value($related = $this->resolveRelationships($request)), $related), diff --git a/src/Services/Search/RepositorySearchService.php b/src/Services/Search/RepositorySearchService.php index ded8a592..4edc0ac3 100644 --- a/src/Services/Search/RepositorySearchService.php +++ b/src/Services/Search/RepositorySearchService.php @@ -2,8 +2,10 @@ namespace Binaryk\LaravelRestify\Services\Search; -use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Filter; +use Binaryk\LaravelRestify\Filters\MatchFilter; +use Binaryk\LaravelRestify\Filters\SearchableFilter; +use Binaryk\LaravelRestify\Filters\SortableFilter; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Matchable; use Binaryk\LaravelRestify\Repositories\Repository; @@ -52,67 +54,38 @@ public function prepareMatchFields(RestifyRequest $request, $query, $extra = []) continue; } - $field = $model->qualifyColumn($key); - - if ($match === 'null') { - if ($negation) { - $query->whereNotNull($field); - } else { - $query->whereNull($field); - } - } else { - switch ($this->repository->getMatchByFields()[$key]) { - case RestifySearchable::MATCH_TEXT: - case 'string': - $query->where($field, $negation ? '!=' : '=', $match); - break; - case RestifySearchable::MATCH_BOOL: - case 'boolean': - if ($match === 'false') { - $query->where(function ($query) use ($field, $negation) { - if ($negation) { - return $query->where($field, true); - } else { - return $query->where($field, '=', false)->orWhereNull($field); - } - }); - break; - } - $query->where($field, $negation ? '!=' : '=', true); - break; - case RestifySearchable::MATCH_INTEGER: - case 'number': - case 'int': - $query->where($field, $negation ? '!=' : '=', (int) $match); - break; - case RestifySearchable::MATCH_DATETIME: - $query->whereDate($field, $negation ? '!=' : '=', $match); - break; - case RestifySearchable::MATCH_ARRAY: - $match = explode(',', $match); - - if ($negation) { - $query->whereNotIn($field, $match); - } else { - $query->whereIn($field, $match); - } - break; - default: - if (is_callable($this->repository->getMatchByFields($request)[$key])) { - call_user_func_array($this->repository->getMatchByFields($request)[$key], [ - $request, $query, - ]); - } - - if (is_subclass_of($this->repository->getMatchByFields($request)[$key], Matchable::class)) { - call_user_func_array([ - app($this->repository->getMatchByFields($request)[$key]), 'handle', - ], [ - $request, $query, - ]); - } - } + if (is_callable($type)) { + call_user_func_array($type, [ + $request, $query, + ]); + + continue; } + + if (is_subclass_of($type, Matchable::class)) { + call_user_func_array([ + app($type), 'handle', + ], [ + $request, $query, + ]); + + continue; + } + + $filter = $type instanceof Filter + ? $type + : MatchFilter::make()->setType($type); + + $filter->setRepository($this->repository) + ->setColumn( + $filter->column ?? $model->qualifyColumn($key) + ); + + call_user_func_array([ + $filter, 'filter', + ], [ + $request, $query, $match, + ]); } return $query; @@ -130,12 +103,12 @@ public function prepareOrders(RestifyRequest $request, $query, $extra = []) if (is_array($params) === true && empty($params) === false) { foreach ($params as $param) { - $this->setOrder($query, $param); + $this->setOrder($request, $query, $param); } } if (empty($params) === true) { - $this->setOrder($query, '+'.$this->repository->newModel()->getKeyName()); + $this->setOrder($request, $query, '+'.$this->repository->newModel()->getKeyName()); } return $query; @@ -164,7 +137,7 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = [] $model = $query->getModel(); - $query->where(function ($query) use ($search, $model) { + $query->where(function ($query) use ($search, $model, $request) { $connectionType = $model->getConnection()->getDriverName(); $canSearchPrimaryKey = is_numeric($search) && @@ -176,17 +149,27 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = [] $query->orWhere($query->getModel()->getQualifiedKeyName(), $search); } - $likeOperator = $connectionType == 'pgsql' ? 'ilike' : 'like'; + foreach ($this->repository->getSearchableFields() as $key => $column) { + $filter = $column instanceof Filter + ? $column + : SearchableFilter::make()->setColumn( + $model->qualifyColumn(is_numeric($key) ? $column : $key) + ); - foreach ($this->repository->getSearchableFields() as $column) { - $query->orWhere($model->qualifyColumn($column), $likeOperator, '%'.$search.'%'); + $filter + ->setRepository($this->repository) + ->setColumn( + $filter->column ?? $model->qualifyColumn(is_numeric($key) ? $column : $key) + ); + + $filter->filter($request, $query, $search); } }); return $query; } - public function setOrder($query, $param) + public function setOrder(RestifyRequest $request, $query, $param) { if ($param === 'random') { $query->inRandomOrder(); @@ -212,14 +195,17 @@ public function setOrder($query, $param) $field = $field ?? $this->repository->newModel()->getKeyName(); if (isset($field)) { - if (in_array($field, $this->repository->getOrderByFields()) === true) { - if ($order === '-') { - $query->orderBy($field, 'desc'); - } - - if ($order === '+') { - $query->orderBy($field, 'asc'); - } + foreach ($this->repository->getOrderByFields() as $column => $definitionField) { + $filter = $definitionField instanceof Filter + ? $definitionField + : SortableFilter::make(); + + $filter->setRepository($this->repository) + ->setColumn( + $filter->column ?? $query->qualifyColumn($field) + ); + + $filter->filter($request, $query, $order); } if ($field === 'random') { diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index 54387a61..5650fc53 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -2,6 +2,12 @@ namespace Binaryk\LaravelRestify\Traits; +use Binaryk\LaravelRestify\Filter; +use Binaryk\LaravelRestify\Filters\MatchFilter; +use Binaryk\LaravelRestify\Filters\SearchableFilter; +use Binaryk\LaravelRestify\Filters\SortableFilter; +use Illuminate\Support\Collection; + /** * @author Eduard Lupacescu */ @@ -55,4 +61,41 @@ public static function getOrderByFields() ? [static::newModel()->getQualifiedKeyName()] : static::$sort; } + + public static function collectFilters($type): Collection + { + $filters = collect([ + SearchableFilter::uriKey() => static::getSearchableFields(), + MatchFilter::uriKey() => static::getMatchByFields(), + SortableFilter::uriKey() => static::getOrderByFields(), + ])->get($type); + + $base = collect([ + SearchableFilter::uriKey() => SearchableFilter::class, + MatchFilter::uriKey() => MatchFilter::class, + SortableFilter::uriKey() => SortableFilter::class, + ])->get($type); + + if (! is_subclass_of($base, Filter::class)) { + return collect([]); + } + + return collect($filters)->map(function ($type, $column) use ($base) { + if (is_numeric($column)) { + /* + * This will handle for example searchables/sortables, where the definition is: + * $search = ['title'] + * */ + $column = $type; + $type = null; + } + + return $type instanceof Filter + ? tap($type, fn ($filter) => $filter->column = $filter->column ?? $column) + : tap(new $base, function ($filter) use ($column, $type) { + $filter->type = $type; + $filter->column = $column; + }); + })->values(); + } } diff --git a/tests/Actions/PerformActionsControllerTest.php b/tests/Actions/PerformActionsControllerTest.php index 924beb04..7e6d943f 100644 --- a/tests/Actions/PerformActionsControllerTest.php +++ b/tests/Actions/PerformActionsControllerTest.php @@ -30,8 +30,8 @@ public function test_could_perform_action_for_multiple_repositories() 'data', ]); - $this->assertEquals(2, PublishPostAction::$applied[0][0]->id); - $this->assertEquals(1, PublishPostAction::$applied[0][1]->id); + $this->assertEquals(1, PublishPostAction::$applied[0][0]->id); + $this->assertEquals(2, PublishPostAction::$applied[0][1]->id); } public function test_cannot_apply_a_show_action_to_index() diff --git a/tests/Feature/Filters/FilterDefinitionTest.php b/tests/Feature/Filters/FilterDefinitionTest.php new file mode 100644 index 00000000..b8c16d9b --- /dev/null +++ b/tests/Feature/Filters/FilterDefinitionTest.php @@ -0,0 +1,37 @@ + 'string', + 'user_id' => MatchFilter::make() + ->setType('int') + ->setRelatedRepositoryKey(UserRepository::uriKey()), + ]; + + PostRepository::$search = [ + 'title' => SearchableFilter::make()->setType('string'), + ]; + + PostRepository::$sort = [ + 'id' => SortableFilter::make()->setType('int'), + 'title', + ]; + + $this->getJson('posts/filters?only=matches,searchables,sortables') + ->assertJsonFragment([ + 'related_repository_key' => 'users', + ]); + } +} diff --git a/tests/Feature/RepositorySearchServiceTest.php b/tests/Feature/RepositorySearchServiceTest.php index a21f9cd0..7d23ad3f 100644 --- a/tests/Feature/RepositorySearchServiceTest.php +++ b/tests/Feature/RepositorySearchServiceTest.php @@ -3,6 +3,9 @@ namespace Binaryk\LaravelRestify\Tests\Feature; use Binaryk\LaravelRestify\Contracts\RestifySearchable; +use Binaryk\LaravelRestify\Filters\MatchFilter; +use Binaryk\LaravelRestify\Filters\SearchableFilter; +use Binaryk\LaravelRestify\Filters\SortableFilter; use Binaryk\LaravelRestify\Services\Search\RepositorySearchService; use Binaryk\LaravelRestify\Tests\Fixtures\User\User; use Binaryk\LaravelRestify\Tests\Fixtures\User\UserRepository; @@ -56,6 +59,64 @@ public function test_can_match_array() ->assertJsonCount(1, 'data'); } + public function test_match_definition_hit_filter_method() + { + factory(User::class, 4)->create(); + + UserRepository::$match = [ + 'id' => MatchFilter::make()->setType(RestifySearchable::MATCH_ARRAY), + ]; + + $this->getJson('users?id=1,2,3') + ->assertJsonCount(3, 'data'); + + $this->getJson('users?-id=1,2,3') + ->assertJsonCount(1, 'data'); + } + + public function test_can_search_using_filter_searchable_definition() + { + factory(User::class, 4)->create([ + 'name' => 'John Doe', + ]); + + factory(User::class, 4)->create([ + 'name' => 'wew', + ]); + + UserRepository::$search = [ + 'name' => SearchableFilter::make(), + ]; + + $this->getJson('users?search=John') + ->assertJsonCount(4, 'data'); + } + + public function test_can_order_using_filter_sortable_definition() + { + factory(User::class)->create([ + 'name' => 'Zoro', + ]); + + factory(User::class)->create([ + 'name' => 'Alisa', + ]); + + UserRepository::$sort = [ + 'name' => SortableFilter::make()->setColumn('name'), + ]; + + $this->assertSame('Alisa', $this->getJson('users?sort=name') + ->json('data.0.attributes.name')); + $this->assertSame('Zoro', $this->getJson('users?sort=name') + ->json('data.1.attributes.name')); + + $this->assertSame('Zoro', $this->getJson('users?sort=-name') + ->json('data.0.attributes.name')); + $this->assertSame('Alisa', $this->getJson('users?sort=-name') + ->json('data.1.attributes.name')); + } + public function test_can_match_closure() { factory(User::class, 4)->create();