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/DeleteManyMutation.php b/src/Mutations/DeleteManyMutation.php new file mode 100644 index 00000000..c591459a --- /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().'Filter'), + ]; + } + + /** + * 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/Mutations/UpdateManyMutation.php b/src/Mutations/UpdateManyMutation.php new file mode 100644 index 00000000..748b9189 --- /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().'Filter'), + '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..d95f61bc 100644 --- a/src/Support/Schema.php +++ b/src/Support/Schema.php @@ -16,6 +16,8 @@ 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; use GraphQL\Type\Schema as GraphQLSchema; @@ -421,8 +423,14 @@ 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); + + $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..ef488c53 --- /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)); + } +} diff --git a/tests/Feature/UpdateManyMutationTest.php b/tests/Feature/UpdateManyMutationTest.php new file mode 100644 index 00000000..f10df158 --- /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); + } +}