From 1e811cd7caf2003d6fbd5ef4c053571f6c488cd5 Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Tue, 16 Apr 2019 08:54:50 +0200 Subject: [PATCH 1/5] Implement update many mutation --- src/Eloquent/ModelSchema.php | 10 ++ src/Mutations/UpdateManyMutation.php | 111 +++++++++++++++++++++++ src/Support/Schema.php | 4 + tests/Feature/UpdateManyMutationTest.php | 62 +++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 src/Mutations/UpdateManyMutation.php create mode 100644 tests/Feature/UpdateManyMutationTest.php diff --git a/src/Eloquent/ModelSchema.php b/src/Eloquent/ModelSchema.php index f820423f..832a7653 100644 --- a/src/Eloquent/ModelSchema.php +++ b/src/Eloquent/ModelSchema.php @@ -184,6 +184,16 @@ public function typename(): string return $this->getTypename(); } + /** + * Get the plural typename for the model. + * + * @return string + */ + public function pluralTypename(): string + { + return Utils::pluralTypename($this->getModel()); + } + /** * Get the key (ID) field. * diff --git a/src/Mutations/UpdateManyMutation.php b/src/Mutations/UpdateManyMutation.php new file mode 100644 index 00000000..adbd2293 --- /dev/null +++ b/src/Mutations/UpdateManyMutation.php @@ -0,0 +1,111 @@ +modelSchema = $modelSchema; + } elseif (is_string($this->modelSchema)) { + $this->modelSchema = $this->registry->getModelSchema($this->modelSchema); + } + + Utils::invariant( + $this->modelSchema instanceof ModelSchema, + 'Model schema on '.get_class($this).' should be an instance of '.ModelSchema::class + ); + } + + /** + * Get the name of the mutation. + * + * @return string + */ + public function name(): string + { + if (isset($this->name)) { + return $this->name; + } + + return 'updateMany'.$this->modelSchema->pluralTypename(); + } + + /** + * The return type of the mutation. + * + * @return Type + */ + public function type(): Type + { + return $this->registry->int(); + } + + /** + * The arguments of the mutation. + * + * @return array + */ + public function args(): array + { + $inputTypeName = 'Update'.$this->modelSchema->typename().'Input'; + + return [ + 'filter' => $this->registry->type($this->modelSchema->typename()), + 'input' => $this->registry->type($inputTypeName), + ]; + } + + /** + * Resolve the mutation. + * + * @param mixed $root + * @param mixed $args + * @param mixed $context + * @param \GraphQL\Type\Definition\ResolveInfo $info + * @return int + */ + public function resolve($root, array $args, $context, ResolveInfo $info) + { + $input = $args['input']; + + $query = $this->modelSchema->getQuery(); + + $this->applyFilters($query, $args['filter']); + + $count = $query->count(); + + DB::transaction(function () use ($query, $input) { + $query->each(function (Model $model) use ($input) { + $modelSchema = $this->registry->getSchemaForModel($model); + + return $modelSchema->updateIfAuthorized($input); + }); + }); + + return $count; + } +} diff --git a/src/Support/Schema.php b/src/Support/Schema.php index e157ccec..a4da1b2d 100644 --- a/src/Support/Schema.php +++ b/src/Support/Schema.php @@ -16,6 +16,7 @@ use Bakery\Queries\SingleEntityQuery; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ObjectType; +use Bakery\Mutations\UpdateManyMutation; use Bakery\Mutations\AttachPivotMutation; use Bakery\Mutations\DetachPivotMutation; use GraphQL\Type\Schema as GraphQLSchema; @@ -421,6 +422,9 @@ protected function getModelMutations(): Collection $updateMutation = new UpdateMutation($this->registry, $modelSchema); $mutations->put($updateMutation->getName(), $updateMutation); + $updateManyMutation = new UpdateManyMutation($this->registry, $modelSchema); + $mutations->put($updateManyMutation->getName(), $updateManyMutation); + $deleteMutation = new DeleteMutation($this->registry, $modelSchema); $mutations->put($deleteMutation->getName(), $deleteMutation); } diff --git a/tests/Feature/UpdateManyMutationTest.php b/tests/Feature/UpdateManyMutationTest.php new file mode 100644 index 00000000..3438562f --- /dev/null +++ b/tests/Feature/UpdateManyMutationTest.php @@ -0,0 +1,62 @@ +authenticate(); + } + + /** @test */ + public function it_can_update_many_models() + { + [$article] = factory(Article::class, 3)->create(); + + $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter) { updateManyArticles(input: $input, filter: $filter) }', [ + 'input' => [ + 'title' => 'Hello world', + ], + 'filter' => [ + 'title' => $article->title, + ], + ]); + + $response->assertJsonFragment(['updateManyArticles' => 1]); + + [$article, $articleTwo, $articleThree] = Article::all(); + $this->assertEquals('Hello world', $article->title); + $this->assertNotEquals('Hello world', $articleTwo->title); + $this->assertNotEquals('Hello world', $articleThree->title); + } + + /** @test */ + public function it_can_update_many_models_with_relation_filter() + { + [$article] = factory(Article::class, 3)->create(); + + $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter) { updateManyArticles(input: $input, filter: $filter) }', [ + 'input' => [ + 'title' => 'Hello world', + ], + 'filter' => [ + 'user' => [ + 'email' => $article->user->email, + ], + ], + ]); + + $response->assertJsonFragment(['updateManyArticles' => 1]); + + [$article, $articleTwo, $articleThree] = Article::all(); + $this->assertEquals('Hello world', $article->title); + $this->assertNotEquals('Hello world', $articleTwo->title); + $this->assertNotEquals('Hello world', $articleThree->title); + } +} From 33f3623e71ac6eb1b4b9d30222e1ca5ac6f647be Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Tue, 16 Apr 2019 09:48:08 +0000 Subject: [PATCH 2/5] Apply fixes from StyleCI --- src/Mutations/UpdateManyMutation.php | 4 ++-- tests/Feature/UpdateManyMutationTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mutations/UpdateManyMutation.php b/src/Mutations/UpdateManyMutation.php index adbd2293..58250034 100644 --- a/src/Mutations/UpdateManyMutation.php +++ b/src/Mutations/UpdateManyMutation.php @@ -2,14 +2,14 @@ namespace Bakery\Mutations; +use Bakery\Utils\Utils; use Bakery\Eloquent\ModelSchema; use Bakery\Support\TypeRegistry; use Bakery\Traits\FiltersQueries; use Bakery\Types\Definitions\Type; -use Bakery\Utils\Utils; +use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use GraphQL\Type\Definition\ResolveInfo; -use Illuminate\Support\Facades\DB; class UpdateManyMutation extends Mutation { diff --git a/tests/Feature/UpdateManyMutationTest.php b/tests/Feature/UpdateManyMutationTest.php index 3438562f..2a66d84e 100644 --- a/tests/Feature/UpdateManyMutationTest.php +++ b/tests/Feature/UpdateManyMutationTest.php @@ -5,7 +5,7 @@ use Bakery\Tests\IntegrationTest; use Bakery\Tests\Fixtures\Models\Article; -class CreateManyMutationTest extends IntegrationTest +class UpdateManyMutationTest extends IntegrationTest { public function setUp(): void { From 1886d76542d5b8d294ec906eba2b0531a10dcb9c Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Tue, 16 Apr 2019 11:59:30 +0200 Subject: [PATCH 3/5] Implement the deleteMany mutation --- src/Mutations/DeleteManyMutation.php | 107 +++++++++++++++++++++++ src/Support/Schema.php | 4 + tests/Feature/DeleteManyMutationTest.php | 56 ++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 src/Mutations/DeleteManyMutation.php create mode 100644 tests/Feature/DeleteManyMutationTest.php diff --git a/src/Mutations/DeleteManyMutation.php b/src/Mutations/DeleteManyMutation.php new file mode 100644 index 00000000..021a26fb --- /dev/null +++ b/src/Mutations/DeleteManyMutation.php @@ -0,0 +1,107 @@ +modelSchema = $modelSchema; + } elseif (is_string($this->modelSchema)) { + $this->modelSchema = $this->registry->getModelSchema($this->modelSchema); + } + + Utils::invariant( + $this->modelSchema instanceof ModelSchema, + 'Model schema on '.get_class($this).' should be an instance of '.ModelSchema::class + ); + } + + /** + * Get the name of the mutation. + * + * @return string + */ + public function name(): string + { + if (isset($this->name)) { + return $this->name; + } + + return 'deleteMany'.$this->modelSchema->pluralTypename(); + } + + /** + * The return type of the mutation. + * + * @return Type + */ + public function type(): Type + { + return $this->registry->int(); + } + + /** + * The arguments of the mutation. + * + * @return array + */ + public function args(): array + { + return [ + 'filter' => $this->registry->type($this->modelSchema->typename()), + ]; + } + + /** + * Resolve the mutation. + * + * @param mixed $root + * @param mixed $args + * @param mixed $context + * @param \GraphQL\Type\Definition\ResolveInfo $info + * @return int + */ + public function resolve($root, array $args, $context, ResolveInfo $info) + { + $query = $this->modelSchema->getQuery(); + + $this->applyFilters($query, $args['filter']); + + $count = $query->count(); + + DB::transaction(function () use ($query) { + $query->each(function (Model $model) { + $modelSchema = $this->registry->getSchemaForModel($model); + + $modelSchema->authorizeToDelete(); + $model->delete(); + }); + }); + + return $count; + } +} diff --git a/src/Support/Schema.php b/src/Support/Schema.php index a4da1b2d..7ac4f2bb 100644 --- a/src/Support/Schema.php +++ b/src/Support/Schema.php @@ -2,6 +2,7 @@ namespace Bakery\Support; +use Bakery\Mutations\DeleteManyMutation; use Bakery\Types; use Bakery\Utils\Utils; use GraphQL\Type\SchemaConfig; @@ -427,6 +428,9 @@ protected function getModelMutations(): Collection $deleteMutation = new DeleteMutation($this->registry, $modelSchema); $mutations->put($deleteMutation->getName(), $deleteMutation); + + $deleteManyMutation = new DeleteManyMutation($this->registry, $modelSchema); + $mutations->put($deleteManyMutation->getName(), $deleteManyMutation); } foreach ($pivotRelations as $relation) { diff --git a/tests/Feature/DeleteManyMutationTest.php b/tests/Feature/DeleteManyMutationTest.php new file mode 100644 index 00000000..2b0c9275 --- /dev/null +++ b/tests/Feature/DeleteManyMutationTest.php @@ -0,0 +1,56 @@ +authenticate(); + } + + /** @test */ + public function it_can_delete_many_models() + { + [$article, $articleTwo, $articleThree] = factory(Article::class, 3)->create(); + + $response = $this->graphql('mutation($filter: ArticleFilter) { deleteManyArticles(filter: $filter) }', [ + 'filter' => [ + 'title' => $article->title, + ], + ]); + + $response->assertJsonFragment(['deleteManyArticles' => 1]); + + $articles = Article::all(); + $this->assertFalse($articles->contains($article)); + $this->assertTrue($articles->contains($articleTwo)); + $this->assertTrue($articles->contains($articleThree)); + } + + /** @test */ + public function it_can_delete_many_models_with_relation_filter() + { + [$article, $articleTwo, $articleThree] = factory(Article::class, 3)->create(); + + $response = $this->graphql('mutation($filter: ArticleFilter) { deleteManyArticles(filter: $filter) }', [ + 'filter' => [ + 'user' => [ + 'email' => $article->user->email, + ], + ], + ]); + + $response->assertJsonFragment(['deleteManyArticles' => 1]); + + $articles = Article::all(); + $this->assertFalse($articles->contains($article)); + $this->assertTrue($articles->contains($articleTwo)); + $this->assertTrue($articles->contains($articleThree)); + } +} From 373da665ef254885b8e9da05298e2c8cd9e3b3e6 Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Tue, 16 Apr 2019 09:59:42 +0000 Subject: [PATCH 4/5] Apply fixes from StyleCI --- src/Support/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Schema.php b/src/Support/Schema.php index 7ac4f2bb..d95f61bc 100644 --- a/src/Support/Schema.php +++ b/src/Support/Schema.php @@ -2,7 +2,6 @@ namespace Bakery\Support; -use Bakery\Mutations\DeleteManyMutation; use Bakery\Types; use Bakery\Utils\Utils; use GraphQL\Type\SchemaConfig; @@ -17,6 +16,7 @@ use Bakery\Queries\SingleEntityQuery; use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\ObjectType; +use Bakery\Mutations\DeleteManyMutation; use Bakery\Mutations\UpdateManyMutation; use Bakery\Mutations\AttachPivotMutation; use Bakery\Mutations\DetachPivotMutation; From c33511c4874dd2771a6ddaf26de9157b9b0ff63d Mon Sep 17 00:00:00 2001 From: Erik Gaal Date: Wed, 29 May 2019 15:42:13 +0200 Subject: [PATCH 5/5] Make the filter types non-nullable --- src/Mutations/DeleteManyMutation.php | 2 +- src/Mutations/UpdateManyMutation.php | 2 +- tests/Feature/DeleteManyMutationTest.php | 4 ++-- tests/Feature/UpdateManyMutationTest.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mutations/DeleteManyMutation.php b/src/Mutations/DeleteManyMutation.php index 021a26fb..c591459a 100644 --- a/src/Mutations/DeleteManyMutation.php +++ b/src/Mutations/DeleteManyMutation.php @@ -72,7 +72,7 @@ public function type(): Type public function args(): array { return [ - 'filter' => $this->registry->type($this->modelSchema->typename()), + 'filter' => $this->registry->type($this->modelSchema->typename().'Filter'), ]; } diff --git a/src/Mutations/UpdateManyMutation.php b/src/Mutations/UpdateManyMutation.php index 58250034..748b9189 100644 --- a/src/Mutations/UpdateManyMutation.php +++ b/src/Mutations/UpdateManyMutation.php @@ -74,7 +74,7 @@ public function args(): array $inputTypeName = 'Update'.$this->modelSchema->typename().'Input'; return [ - 'filter' => $this->registry->type($this->modelSchema->typename()), + 'filter' => $this->registry->type($this->modelSchema->typename().'Filter'), 'input' => $this->registry->type($inputTypeName), ]; } diff --git a/tests/Feature/DeleteManyMutationTest.php b/tests/Feature/DeleteManyMutationTest.php index 2b0c9275..ef488c53 100644 --- a/tests/Feature/DeleteManyMutationTest.php +++ b/tests/Feature/DeleteManyMutationTest.php @@ -19,7 +19,7 @@ public function it_can_delete_many_models() { [$article, $articleTwo, $articleThree] = factory(Article::class, 3)->create(); - $response = $this->graphql('mutation($filter: ArticleFilter) { deleteManyArticles(filter: $filter) }', [ + $response = $this->graphql('mutation($filter: ArticleFilter!) { deleteManyArticles(filter: $filter) }', [ 'filter' => [ 'title' => $article->title, ], @@ -38,7 +38,7 @@ public function it_can_delete_many_models_with_relation_filter() { [$article, $articleTwo, $articleThree] = factory(Article::class, 3)->create(); - $response = $this->graphql('mutation($filter: ArticleFilter) { deleteManyArticles(filter: $filter) }', [ + $response = $this->graphql('mutation($filter: ArticleFilter!) { deleteManyArticles(filter: $filter) }', [ 'filter' => [ 'user' => [ 'email' => $article->user->email, diff --git a/tests/Feature/UpdateManyMutationTest.php b/tests/Feature/UpdateManyMutationTest.php index 2a66d84e..f10df158 100644 --- a/tests/Feature/UpdateManyMutationTest.php +++ b/tests/Feature/UpdateManyMutationTest.php @@ -19,7 +19,7 @@ public function it_can_update_many_models() { [$article] = factory(Article::class, 3)->create(); - $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter) { updateManyArticles(input: $input, filter: $filter) }', [ + $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter!) { updateManyArticles(input: $input, filter: $filter) }', [ 'input' => [ 'title' => 'Hello world', ], @@ -41,7 +41,7 @@ public function it_can_update_many_models_with_relation_filter() { [$article] = factory(Article::class, 3)->create(); - $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter) { updateManyArticles(input: $input, filter: $filter) }', [ + $response = $this->graphql('mutation($input: UpdateArticleInput!, $filter: ArticleFilter!) { updateManyArticles(input: $input, filter: $filter) }', [ 'input' => [ 'title' => 'Hello world', ],