Repository: Lomkit/laravel-rest-api (php)

Issue:
Validation rule for relationships applied to an attach operation
### Laravel Rest Api Version

2.1.0

### Laravel Version

10.24.0

### PHP Version

8.1

### Database Driver & Version

_No response_

### Description

My Site resource has a BelongsTo relationship with another Client resource.

When creating my Site, I specify the Client relationship to attach to it.

The problem is that the validation rules specified in the Client resource are requested and I get an error.

`
"message": "The mutate.0.relations.client.attributes.name field is required. (and 1 more error)",
    "errors": {
        "mutate.0.relations.client.attributes.name": [
            "The mutate.0.relations.client.attributes.name field is required."
        ]
    }
`

### Steps To Reproduce

- Create SiteResource
- Create ClientResource
- Specifie relation (BelongsTo) with ClientResource
- Add validations rules in ClientResource (required validation)
- Create Site with sites/mutate
`
"mutate": [
        {
            "operation" : "create",
            "attributes": {
                "name": "Test",
                "timezone": "Europe/Paris"
            },
            "relations": {
                "client": {
                    "operation": "attach",
                    "key": "id client"
                }
            }
        }
    ]
`

Pull request:
Fix/relation rules triggered on attach detach
closes #67 

Patch:
diff --git a/src/Rules/CustomRulable.php b/src/Rules/CustomRulable.php
index 9abb365..415fe88 100644
--- a/src/Rules/CustomRulable.php
+++ b/src/Rules/CustomRulable.php
@@ -58,10 +58,13 @@ class CustomRulable implements ValidationRule, ValidatorAwareRule
             $rules = $this->resource->createRules(
                 app()->make(RestRequest::class)
             );
-        } else {
+        } elseif ($value['operation'] === 'update') {
             $rules = $this->resource->updateRules(
                 app()->make(RestRequest::class)
             );
+        } else {
+            // No rules needed
+            return;
         }
 
         $rules = array_merge_recursive(


Test patch:
diff --git a/tests/Feature/Controllers/MutateUpdateOperationsTest.php b/tests/Feature/Controllers/MutateUpdateOperationsTest.php
index a0f5c3f..e3efb64 100644
--- a/tests/Feature/Controllers/MutateUpdateOperationsTest.php
+++ b/tests/Feature/Controllers/MutateUpdateOperationsTest.php
@@ -594,6 +594,60 @@ class MutateUpdateOperationsTest extends TestCase
         );
     }
 
+    public function test_updating_a_resource_with_attaching_has_many_relation_with_required_rules(): void
+    {
+        $modelToUpdate = ModelFactory::new()->createOne();
+        $hasManyRelationToAttach = HasManyRelationFactory::new()
+            ->createOne();
+
+        Gate::policy(Model::class, GreenPolicy::class);
+        Gate::policy(HasManyRelation::class, GreenPolicy::class);
+        Gate::policy(BelongsToManyRelation::class, GreenPolicy::class);
+
+        $response = $this->post(
+            '/api/constrained/mutate',
+            [
+                'mutate' => [
+                    [
+                        'operation'  => 'update',
+                        'key'        => $modelToUpdate->getKey(),
+                        'attributes' => [
+                            'name'   => 'new name',
+                            'number' => 5001,
+                        ],
+                        'relations' => [
+                            'belongsToManyRelation' => [
+                                [
+                                    'operation'  => 'create',
+                                    'attributes' => [],
+                                ],
+                            ],
+                            'hasManyRelation' => [
+                                [
+                                    'operation' => 'attach',
+                                    'key'       => $hasManyRelationToAttach->getKey(),
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+            ['Accept' => 'application/json']
+        );
+
+        $this->assertMutatedResponse(
+            $response,
+            [],
+            [$modelToUpdate],
+        );
+
+        // Here we test that the relation is correctly linked
+        $this->assertEquals(
+            Model::find($response->json('updated.0'))->hasManyRelation()->count(),
+            1
+        );
+    }
+
     public function test_updating_a_resource_with_detaching_has_many_relation(): void
     {
         $modelToUpdate = ModelFactory::new()->createOne();
@@ -1191,6 +1245,60 @@ class MutateUpdateOperationsTest extends TestCase
         );
     }
 
+    public function test_updating_a_resource_with_updating_belongs_to_many_relation_pivot_fields(): void
+    {
+        $modelToUpdate = ModelFactory::new()->createOne();
+
+        $belongsToManyToUpdate = BelongsToManyRelationFactory::new()->createOne();
+
+        Gate::policy(Model::class, GreenPolicy::class);
+        Gate::policy(BelongsToManyRelation::class, GreenPolicy::class);
+
+        $response = $this->post(
+            '/api/models/mutate',
+            [
+                'mutate' => [
+                    [
+                        'operation'  => 'update',
+                        'key'        => $modelToUpdate->getKey(),
+                        'attributes' => [
+                            'name'   => 'new name',
+                            'number' => 5001,
+                        ],
+                        'relations' => [
+                            'belongsToManyRelation' => [
+                                [
+                                    'operation'  => 'update',
+                                    'key'        => $belongsToManyToUpdate->getKey(),
+                                    'pivot'      => [
+                                        'number' => 20,
+                                    ],
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+            ],
+            ['Accept' => 'application/json']
+        );
+
+        $this->assertMutatedResponse(
+            $response,
+            [],
+            [$modelToUpdate]
+        );
+
+        // Here we test that the relation is correctly linked
+        $this->assertEquals(
+            Model::find($response->json('updated.0'))->belongsToManyRelation()->count(),
+            1
+        );
+        $this->assertEquals(
+            Model::find($response->json('updated.0'))->belongsToManyRelation[0]->belongs_to_many_pivot->number,
+            20
+        );
+    }
+
     public function test_updating_a_resource_with_creating_multiple_belongs_to_many_relation(): void
     {
         $modelToUpdate = ModelFactory::new()->createOne();
diff --git a/tests/Support/Rest/Resources/ConstrainedHasManyResource.php b/tests/Support/Rest/Resources/ConstrainedHasManyResource.php
new file mode 100644
index 0000000..ad3c587
--- /dev/null
+++ b/tests/Support/Rest/Resources/ConstrainedHasManyResource.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Lomkit\Rest\Tests\Support\Rest\Resources;
+
+use Lomkit\Rest\Concerns\Resource\DisableAutomaticGates;
+use Lomkit\Rest\Http\Requests\RestRequest;
+use Lomkit\Rest\Http\Resource;
+use Lomkit\Rest\Tests\Support\Models\HasManyRelation;
+
+class ConstrainedHasManyResource extends Resource
+{
+    use DisableAutomaticGates;
+    public static $model = HasManyRelation::class;
+
+    public function rules(RestRequest $request)
+    {
+        return [
+            'number' => 'required',
+        ];
+    }
+
+    public function relations(RestRequest $request): array
+    {
+        return [];
+    }
+
+    public function fields(RestRequest $request): array
+    {
+        return [
+            'id',
+            'number',
+        ];
+    }
+}
diff --git a/tests/Support/Rest/Resources/ConstrainedResource.php b/tests/Support/Rest/Resources/ConstrainedResource.php
index 0f763ee..9b769a4 100644
--- a/tests/Support/Rest/Resources/ConstrainedResource.php
+++ b/tests/Support/Rest/Resources/ConstrainedResource.php
@@ -6,6 +6,7 @@ use Lomkit\Rest\Http\Requests\RestRequest;
 use Lomkit\Rest\Http\Resource;
 use Lomkit\Rest\Relations\BelongsTo;
 use Lomkit\Rest\Relations\BelongsToMany;
+use Lomkit\Rest\Relations\HasMany;
 use Lomkit\Rest\Tests\Support\Models\Model;
 
 class ConstrainedResource extends Resource
@@ -21,6 +22,7 @@ class ConstrainedResource extends Resource
             BelongsTo::make('belongsToRelation', BelongsToResource::class)
                 ->prohibitedOnCreation()
                 ->prohibitedOnUpdate(),
+            HasMany::make('hasManyRelation', ConstrainedHasManyResource::class),
         ];
     }