Skip to content

Commit 8326d80

Browse files
spawniaclaude
andcommitted
Throw error when creating hasOne relation that already exists
Previously, using `create` on a hasOne relation when a related model already exists would silently create a second record, corrupting the relationship. Now throws a GraphQL error guiding users to use `upsert`. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 833c94e commit 8326d80

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

src/Execution/Arguments/NestedOneToOne.php

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

33
namespace Nuwave\Lighthouse\Execution\Arguments;
44

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

2425
if ($args->has('create')) {
26+
if ($relation->exists()) {
27+
$relatedClass = class_basename($relation->getRelated());
28+
$parentClass = class_basename($model);
29+
30+
throw new Error("Cannot create a related model: a {$relatedClass} already exists for this {$parentClass}. Use upsert to modify the existing model.");
31+
}
32+
2533
$saveModel = new ResolveNested(new SaveModel($relation));
2634

2735
$saveModel($relation->make(), $args->arguments['create']->value);

tests/Integration/Execution/MutationExecutor/HasOneTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,42 @@ public function testUpdateWithNewHasOne(string $action): void
331331
]);
332332
}
333333

334+
/** @dataProvider existingModelMutations */
335+
#[DataProvider('existingModelMutations')]
336+
public function testCreateHasOneWhenAlreadyExists(string $action): void
337+
{
338+
$task = factory(Task::class)->create();
339+
$this->assertInstanceOf(Task::class, $task);
340+
341+
$task->post()
342+
->save(
343+
factory(Post::class)->create(),
344+
);
345+
346+
$this->graphQL(/** @lang GraphQL */ <<<GRAPHQL
347+
mutation {
348+
{$action}Task(input: {
349+
id: 1
350+
name: "foo"
351+
post: {
352+
create: {
353+
title: "bar"
354+
}
355+
}
356+
}) {
357+
id
358+
name
359+
post {
360+
id
361+
title
362+
}
363+
}
364+
}
365+
GRAPHQL)->assertGraphQLErrorMessage('Cannot create a related model: a Post already exists for this Task. Use upsert to modify the existing model.');
366+
367+
$this->assertSame(1, Post::count());
368+
}
369+
334370
/** @dataProvider existingModelMutations */
335371
#[DataProvider('existingModelMutations')]
336372
public function testUpdateAndUpdateHasOne(string $action): void

0 commit comments

Comments
 (0)