Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,19 @@ Tests use `Tests\Utils\` namespace for test fixtures (Models, Queries, Mutations
- Never use `final` in `src/`, always in `tests/`
- Full namespace in PHPDoc (`@var \Full\Namespace\Class`), imports in code
- Code elements with `@api` have stability guarantees between major versions

## Pull Requests

Follow the [PR template](.github/PULL_REQUEST_TEMPLATE.md):
- Link related issues
- Add or update tests
- Document user-facing changes in `/docs`
- Update `CHANGELOG.md`

### Changelog

Add entries to the `## Unreleased` section in [CHANGELOG.md](/CHANGELOG.md).
Use categories: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
End each entry with a full PR URL: `https://github.com/nuwave/lighthouse/pull/<number>`.

See [CONTRIBUTING.md](/CONTRIBUTING.md) for full guidelines.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

### Fixed

- Fix `upsert` for `HasOne` creating a new record instead of updating when no `id` is provided in the input https://github.com/nuwave/lighthouse/pull/2742
- Throw error when using `create` on `HasOne` relation when a related model already exists https://github.com/nuwave/lighthouse/pull/2742

## v6.64.2

### Fixed
Expand Down
10 changes: 9 additions & 1 deletion src/Execution/Arguments/NestedOneToOne.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Nuwave\Lighthouse\Execution\Arguments;

use GraphQL\Error\Error;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Nuwave\Lighthouse\Support\Contracts\ArgResolver;
Expand All @@ -22,6 +23,13 @@ public function __invoke($model, $args): void
assert($relation instanceof HasOne || $relation instanceof MorphOne);

if ($args->has('create')) {
if ($relation->exists()) {
$relatedClass = class_basename($relation->getRelated());
$parentClass = class_basename($model);

throw new Error("Cannot create a related model: a {$relatedClass} already exists for this {$parentClass}. Use upsert to modify the existing model.");
}

$saveModel = new ResolveNested(new SaveModel($relation));

$saveModel($relation->make(), $args->arguments['create']->value);
Expand All @@ -36,7 +44,7 @@ public function __invoke($model, $args): void
if ($args->has('upsert')) {
$upsertModel = new ResolveNested(new UpsertModel(new SaveModel($relation)));

$upsertModel($relation->make(), $args->arguments['upsert']->value);
$upsertModel($relation->first() ?? $relation->make(), $args->arguments['upsert']->value);
}

if ($args->has('delete')) {
Expand Down
84 changes: 84 additions & 0 deletions tests/Integration/Execution/MutationExecutor/HasOneTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,42 @@ public function testUpdateWithNewHasOne(string $action): void
]);
}

/** @dataProvider existingModelMutations */
#[DataProvider('existingModelMutations')]
public function testCreateHasOneWhenAlreadyExists(string $action): void
{
$task = factory(Task::class)->create();
$this->assertInstanceOf(Task::class, $task);

$task->post()
->save(
factory(Post::class)->create(),
);

$this->graphQL(/** @lang GraphQL */ <<<GRAPHQL
mutation {
{$action}Task(input: {
id: 1
name: "foo"
post: {
create: {
title: "bar"
}
}
}) {
id
name
post {
id
title
}
}
}
GRAPHQL)->assertGraphQLErrorMessage('Cannot create a related model: a Post already exists for this Task. Use upsert to modify the existing model.');

$this->assertSame(1, Post::count());
}

/** @dataProvider existingModelMutations */
#[DataProvider('existingModelMutations')]
public function testUpdateAndUpdateHasOne(string $action): void
Expand Down Expand Up @@ -423,6 +459,54 @@ public function testUpdateAndUpsertHasOne(string $action): void
]);
}

/** @dataProvider existingModelMutations */
#[DataProvider('existingModelMutations')]
public function testUpdateAndUpsertExistingHasOneWithoutID(string $action): void
{
$task = factory(Task::class)->create();
$this->assertInstanceOf(Task::class, $task);

$task->post()
->save(
factory(Post::class)->create(),
);

$this->graphQL(/** @lang GraphQL */ <<<GRAPHQL
mutation {
{$action}Task(input: {
id: 1
name: "foo"
post: {
upsert: {
title: "bar"
}
}
}) {
id
name
post {
id
title
}
}
}
GRAPHQL)->assertJson([
'data' => [
"{$action}Task" => [
'id' => '1',
'name' => 'foo',
'post' => [
'id' => '1',
'title' => 'bar',
],
],
],
]);

// Assert that the existing post was updated, not a new one created
$this->assertSame(1, Post::count());
}

/** @dataProvider existingModelMutations */
#[DataProvider('existingModelMutations')]
public function testUpdateAndDeleteHasOne(string $action): void
Expand Down