Skip to content

Define custom mutate response #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

15g-lucas
Copy link
Member

@15g-lucas 15g-lucas commented Apr 5, 2025

closes #151
closes #139

Description

This PR adds the possibility of customizing the response format in the event of a Mutate action.

Main changes

It is now possible to define

  • a $responseFields array containing the fields to be returned in the response.
  • an array $responseRelations containing the relations to be returned in the response

Technical Details

The relations that will be returned, defined in $responseRelations, will in turn be based on their $responseFields (in their own model).
To avoid a breaking change, if the array $responseFields is not defined or empty, the response remains unchanged:
(For example)

{
    "created": [
        137
    ],
    "updated": []
}

Examples

When $responseFields is empty or undefined, the response will look like this:

{
    "created": [
        137
    ],
    "updated": []
}

If $responseFields is set to :

public array $responseFields = [
'id',
'name'
] ;

Then the answer will be something like this:

{
    "created": [
        {
            "name": "Gautier Deleglise",
            "id": 136,
            "user": {
                "id": 1,
                "name": "Guest User"
            }
        }
    ],
    "updated": []
}

If $responseRelations includes:

public array $responseRelations = [
	'user'
];

Then, the response will look like :

{
    "created": [
        {
            "name": "Gautier Deleglise",
            "id": 139,
            "user": {
                "id": 1,
                "name": "Guest User"
            }
        }
    ],
    "updated": []
}

Knowing that $reponseFields in UserResource is defined as follows :

public array $responseFields = [
'id',
'name'
] ;

Tests

If you are happy with the functional part, please let me know and I will proceed with the test coverage.

Docs

If you are satisfied with the functional part, let me know and I will update the documentation.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced the mutation operations to return a structured response that now delivers either a key or a detailed set of attributes and related data.
  • Documentation

    • Improved clarity in parameter descriptions for better user understanding.

Copy link
Contributor

coderabbitai bot commented Apr 5, 2025

Walkthrough

The pull request introduces a new public method formatModelResponse to the PerformMutation trait. This method reformats the mutation result by either returning the model’s key or an array of attributes and related models based on specified fields. The mutate method has been updated to utilize this method, altering the control flow to include the new response formatting step.

Changes

File Change Summary
src/Query/Traits/PerformMutation.php Added formatModelResponse method; updated mutate method to use the new response formatting.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant PM as PerformMutation Trait
    C->>PM: Request mutation
    PM->>PM: Execute applyMutation (perform mutation)
    PM->>PM: Call formatModelResponse (format model output)
    PM-->>C: Return formatted response
Loading

Assessment against linked issues

Objective Addressed Explanation
Return custom object or relations after mutation (#151)
Mutating Objects with relations returns relation's id (#139)

Poem

I'm a rabbit, hopping with cheer,
A new method has sprung, oh so dear!
Formatting responses in a clever new way,
Adjustments made to brighten the day.
Code and carrots—the perfect display!
🥕🐇
Hop on to new changes without delay!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Query/Traits/PerformMutation.php (1)

123-129: Enhance method documentation

The method comments could be more comprehensive to explain the purpose and behavior of this new feature.

/**
- * Modify the response according to $responseFields in the resource
+ * Format the model response based on configured fields and relations
 *
+ * This method customizes the mutation response format:
+ * - If no response fields are defined, returns just the model's primary key (legacy behavior)
+ * - If response fields are defined, returns the specified model attributes
+ * - If response relations are defined, includes related models with their specified fields
 *
 * @param Model $model
 * @return mixed|array
 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6141f9 and b88d8f7.

📒 Files selected for processing (1)
  • src/Query/Traits/PerformMutation.php (5 hunks)
🔇 Additional comments (3)
src/Query/Traits/PerformMutation.php (3)

37-38: Improved response formatting looks good

The update to utilize the new formatModelResponse method instead of directly retrieving the model key is a clean way to implement the custom response feature.


47-47: Documentation improvements are helpful

These documentation comment updates improve clarity by being more specific about parameter types and descriptions.

Also applies to: 80-82


132-136: Backward compatibility implementation looks good

The check for empty response fields preserves backward compatibility by returning just the model key when no fields are specified, which aligns with the PR objectives.

@15g-lucas
Copy link
Member Author

Code Rabbit is very relevant :P

I will correct his feedback :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/Query/Traits/PerformMutation.php (2)

150-154: ⚠️ Potential issue

Fix condition order in relationship check

There's a logic issue in the relationship checking order that could lead to undefined index errors.

- $relationFields = $relation->resource()->responseFields ?? [];
-
- if (empty($relationsInModel[$relationName]) ||  !in_array($relationName, $defaultRelations) || empty($relationFields)) {
+ if (!isset($relationsInModel[$relationName]) || !in_array($relationName, $defaultRelations)) {
      continue;
  }
+
+ $relationFields = $relation->resource()->responseFields ?? [];
+
+ if (empty($relationFields)) {
+     continue;
+ }

This change ensures we first check if the relationship exists in the model before attempting to access it, preventing potential undefined index errors.


156-159: 🛠️ Refactor suggestion

Add support for collection relationships

The current implementation doesn't handle one-to-many relationships correctly, as it treats all relations as single models.

- $relationAttributes = collect($relationsInModel[$relationName])->only($relationFields);
- 
- $formattedRelations[$relationName] = $relationAttributes;
+ $relationModel = $relationsInModel[$relationName];
+
+ if ($relationModel instanceof \Illuminate\Database\Eloquent\Collection) {
+     $formattedRelations[$relationName] = $relationModel->map(function($model) use ($relationFields) {
+         return collect($model->getAttributes())->only($relationFields);
+     })->toArray();
+ } else {
+     $formattedRelations[$relationName] = collect($relationModel->getAttributes())->only($relationFields);
+ }
🧹 Nitpick comments (3)
src/Query/Traits/PerformMutation.php (3)

138-145: Enhance variable naming for clarity

The variable names are generally good, but could be more descriptive to improve code readability.

- $attributes = collect($model->getAttributes())->only($fields);
+ $selectedAttributes = collect($model->getAttributes())->only($fields);

- $relationsInResource = $this->resource->getRelations(app()->make(RestRequest::class));
+ $resourceRelations = $this->resource->getRelations(app()->make(RestRequest::class));

- $relationsInModel = $model->getRelations();
+ $loadedModelRelations = $model->getRelations();

- $formattedRelations = [];
+ $formattedRelationAttributes = [];

161-161: Consider separating attributes and relations in the response structure

Merging attributes and relations directly can lead to property name conflicts. Consider a clearer structure.

- return $attributes->merge($formattedRelations)->toArray();
+ $result = $attributes->toArray();
+ if (!empty($formattedRelations)) {
+     // Option 1: Keep flat structure but with a clearer merge approach
+     foreach ($formattedRelations as $relation => $value) {
+         $result[$relation] = $value;
+     }
+     
+     // Option 2: Nest relations for clearer separation
+     // $result['relations'] = $formattedRelations;
+ }
+ return $result;

A flat structure maintains compatibility with the examples in the PR objectives, but the nested option provides clearer separation if needed in the future.


123-129: Improve method documentation

The PHPDoc can be enhanced with more details about the method's behavior and parameters.

/**
- * Modify the response according to $responseFields in the resource
+ * Format the model response based on configured response fields.
+ * 
+ * If responseFields is defined in the resource, returns an array of model attributes
+ * and related models based on those fields. Otherwise, returns only the model key.
 *
+ * @param Model $model The model to format for response
- * @return mixed|array
+ * @return mixed The model key or an array of attributes and related models
 */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b88d8f7 and ec471b9.

📒 Files selected for processing (1)
  • src/Query/Traits/PerformMutation.php (5 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/Query/Traits/PerformMutation.php (7)
tests/Support/Rest/Resources/ModelWithHooksResource.php (1)
  • fields (37-45)
tests/Support/Rest/Resources/BelongsToResource.php (1)
  • fields (24-30)
tests/Support/Rest/Resources/NoAuthorizationResource.php (1)
  • fields (18-27)
src/Concerns/Resource/ConfiguresRestParameters.php (1)
  • fields (26-29)
src/Relations/Relation.php (1)
  • resource (104-114)
src/Concerns/Resource/Relationable.php (2)
  • getRelations (65-74)
  • relation (19-37)
src/Http/Resource.php (1)
  • newModel (54-60)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests on PHP 8.2 with Laravel ^12.0 and sqlite
🔇 Additional comments (3)
src/Query/Traits/PerformMutation.php (3)

37-38: LGTM - Updated mutate method to use the new response formatter

The refactoring of this code segment is clean and aligns with the PR objective of enhancing response customization. The code now uses the new formatModelResponse method to format the model response based on specified fields.


132-136: LGTM - Backward compatibility is properly maintained

The code correctly checks for the presence of response fields and returns just the model key if none are defined, ensuring backward compatibility as specified in the PR objectives.


145-145:

❓ Verification inconclusive

Verify that responseRelations exists in the resource

The code assumes that $this->resource->responseRelations is defined or accessible, but it's not clear from the provided context if this property exists in the Resource class.


🏁 Script executed:

#!/bin/bash
# Check for the responseRelations property in resource classes

echo "Searching for the 'responseRelations' property in resource classes..."
rg -l "public\s+(array|static\s+array|static)\s+\\\$responseRelations" --type php

echo "Searching for the 'responseFields' property in resource classes..."
rg -l "public\s+(array|static\s+array|static)\s+\\\$responseFields" --type php

Length of output: 464


Action Required: Confirm the Definition of responseRelations on the Resource

The initial grep did not return any results showing an explicit declaration of responseRelations within resource classes. Please verify manually whether responseRelations is:

  • Explicitly defined in any Resource class, or
  • Provided dynamically via a magic method (e.g., __get).

If the latter is true, consider adding documentation or comments to clarify its origin. This will help future maintainers understand the expected structure of the resource.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Query/Traits/PerformMutation.php (2)

130-174: The implementation looks good, but some refinements could improve it.

The method correctly implements the custom response formatting functionality as described in the PR objectives. It properly handles the backward compatibility case and correctly processes both model attributes and relations.

Some suggestions for improvement:

  1. There's an inconsistency in the return type handling between collections and single models:

    • For collections (line 162-164), you map and convert to array
    • For single models (line 166), you don't call toArray() on the collection
  2. Consider adding more robust error handling for edge cases.

  3. The method documentation could be more detailed about the expected return format.

Here's a suggested fix for the inconsistency:

if ($relationModel instanceof \Illuminate\Database\Eloquent\Collection) {
    $formattedRelations[$relationName] = $relationModel->map(function($model) use ($relationFields) {
        return collect($model->getAttributes())->only($relationFields);
    })->toArray();
} else {
-   $formattedRelations[$relationName] = collect($relationModel->getAttributes())->only($relationFields);
+   $formattedRelations[$relationName] = collect($relationModel->getAttributes())->only($relationFields)->toArray();
}

145-149: Consider more efficient relationship matching.

The current approach for finding the relation in resources works, but it relies on table name singularization which might not always match the actual relationship name defined in models.

For better maintainability, consider implementing a more direct way to get relations by name, such as adding a method to retrieve a relation by its name in the resource.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec471b9 and 94a10dd.

📒 Files selected for processing (1)
  • src/Query/Traits/PerformMutation.php (5 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/Query/Traits/PerformMutation.php (7)
tests/Support/Rest/Resources/ModelWithHooksResource.php (1)
  • fields (37-45)
tests/Support/Rest/Resources/BelongsToResource.php (1)
  • fields (24-30)
tests/Support/Rest/Resources/NoAuthorizationResource.php (1)
  • fields (18-27)
src/Concerns/Resource/ConfiguresRestParameters.php (1)
  • fields (26-29)
src/Relations/Relation.php (1)
  • resource (104-114)
src/Concerns/Resource/Relationable.php (2)
  • getRelations (65-74)
  • relation (19-37)
src/Http/Resource.php (1)
  • newModel (54-60)
🔇 Additional comments (3)
src/Query/Traits/PerformMutation.php (3)

38-38: Good refactoring to support custom response formatting.

The modification properly uses the new formatModelResponse method instead of directly using the model key, enabling the custom response format feature.


47-47: Documentation improvements add clarity.

The parameter documentation updates provide better clarity about the expected input parameters.

Also applies to: 81-83


133-135: Ensure response consistency in your API documentation.

The backward compatibility approach is good - when no response fields are defined, it returns just the model key. Make sure this difference in response format is well-documented in your API documentation to avoid confusion for API consumers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Query/Traits/PerformMutation.php (1)

137-169: Relation handling approach could be more robust.

The implementation for handling relations works correctly but relies on table name singularization to match relation names, which might not always be reliable across all models.

Consider implementing a more direct mapping between relation names and table names, or adding a configurable mapping option to handle edge cases where the singularized table name doesn't match the relation name:

- $relation = collect($relationsInResource)->first(function ($rel) use ($relationName) {
-     return Str::singular($rel->resource()::newModel()->getTable()) === $relationName;
- });
+ $relation = collect($relationsInResource)->first(function ($rel) use ($relationName) {
+     // Try direct match first
+     if ($rel->relation === $relationName) {
+         return true;
+     }
+     // Fall back to table name singularization
+     return Str::singular($rel->resource()::newModel()->getTable()) === $relationName;
+ });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22b3c28 and 96676ce.

📒 Files selected for processing (1)
  • src/Query/Traits/PerformMutation.php (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/Query/Traits/PerformMutation.php (7)
tests/Support/Rest/Resources/ModelWithHooksResource.php (1)
  • fields (37-45)
tests/Support/Rest/Resources/BelongsToResource.php (1)
  • fields (24-30)
tests/Support/Rest/Resources/NoAuthorizationResource.php (1)
  • fields (18-27)
src/Concerns/Resource/ConfiguresRestParameters.php (1)
  • fields (26-29)
src/Relations/Relation.php (1)
  • resource (104-114)
src/Concerns/Resource/Relationable.php (2)
  • getRelations (65-74)
  • relation (19-37)
src/Http/Resource.php (1)
  • newModel (54-60)
🔇 Additional comments (3)
src/Query/Traits/PerformMutation.php (3)

38-39: Improved response formatting is properly integrated.

The change properly integrates the new formatModelResponse method into the mutation flow, enhancing the response format while maintaining backward compatibility.


124-136: Good implementation of the response formatting with backward compatibility.

The method correctly implements the feature described in the PR objectives, allowing for customized response fields while maintaining backward compatibility by falling back to returning just the model key when no response fields are defined.


170-173: The result merging approach is clear and maintainable.

This approach of building the result array is clear and easy to understand, making it more maintainable than directly merging collections.

15g-lucas and others added 2 commits April 6, 2025 15:04
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Query/Traits/PerformMutation.php (1)

131-181: Consider adding documentation and unit tests.

While the implementation looks solid, it would benefit from thorough unit tests to ensure all edge cases are covered, especially for relationship handling.

For example:

  • Test with empty responseFields
  • Test with only model fields, no relations
  • Test with relations that don't exist in the model
  • Test with null relations
  • Test with collection relations

I can help draft unit tests for this new functionality if you'd like.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96676ce and a36338f.

📒 Files selected for processing (1)
  • src/Query/Traits/PerformMutation.php (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/Query/Traits/PerformMutation.php (7)
tests/Support/Rest/Resources/ModelWithHooksResource.php (1)
  • fields (37-45)
tests/Support/Rest/Resources/BelongsToResource.php (1)
  • fields (24-30)
tests/Support/Rest/Resources/NoAuthorizationResource.php (1)
  • fields (18-27)
src/Concerns/Resource/ConfiguresRestParameters.php (1)
  • fields (26-29)
src/Relations/Relation.php (1)
  • resource (104-114)
src/Concerns/Resource/Relationable.php (2)
  • getRelations (65-74)
  • relation (19-37)
src/Http/Resource.php (1)
  • newModel (54-60)
🔇 Additional comments (8)
src/Query/Traits/PerformMutation.php (8)

5-7: New imports added appropriately.

The added imports (Collection and Str) support the new functionality for custom response formatting.


38-39: Good update to utilize the new response formatting.

The modified mutate method now correctly uses the new formatModelResponse method instead of directly retrieving the model key, enabling the customized response format while maintaining the same overall structure.


124-136: Good backward compatibility implementation.

The new formatModelResponse method correctly checks for the presence of $responseFields and falls back to the original behavior (returning just the model key) when not defined, maintaining backward compatibility as specified in the PR objectives.


138-142: Well-structured initialization of relations.

The code appropriately retrieves relations from both the resource and model, and initializes the necessary variables for relation formatting.


143-150: Good error handling for edge cases.

The implementation correctly checks for missing or null relations, preventing potential errors when dealing with incomplete relation data.


167-173: Good handling of both collection and single model relations.

The implementation correctly differentiates between collections and single models, and formats them consistently by ensuring both return arrays.


175-180: Effective result composition.

The code properly constructs the final response by combining model attributes with formatted relations. This approach maintains the expected response structure while adding the requested customization.


152-163:

❓ Verification inconclusive

Relationship discovery may be brittle.

The current implementation for finding the matching relation relies on table name singularization, which might not always match the actual relation name defined in models.

Consider if there's a more direct way to match relations by name rather than relying on table name conversion. Run the following to check relation matching in the codebase:


🏁 Script executed:

#!/bin/bash
# Find relation definitions that might use custom names that wouldn't match with table singularization
rg -A 2 "function \w+\(\)" --type php | rg -B 1 "return \$this->belongsTo|return \$this->hasMany|return \$this->hasOne|return \$this->belongsToMany"

Length of output: 146


Action Required: Verify Relation Matching Logic in PerformMutation Trait

The current implementation in src/Query/Traits/PerformMutation.php (lines 152–163) uses Str::singular on the model's table name to identify the target relation. This approach can be brittle since it assumes that the singularized table name always matches the defined relation name—even though custom naming patterns might be used in models.

  • The code snippet under review:
                $relation = collect($relationsInResource)->first(function ($rel) use ($relationName) {
                    return Str::singular($rel->resource()::newModel()->getTable()) === $relationName;
                });
                
                if (!$relation) {
                    continue;
                }
                
                $relationFields = $relation->resource()->responseFields ?? [];
                if (empty($relationFields)) {
                    continue;
                }
  • Concern: Relying solely on table name singularization may fail when models define relations with custom names that do not conform to typical table naming conventions.
  • Current Verification: An initial search for custom relation definitions (using an rg command to look for relation methods returning belongsTo, hasMany, etc.) produced no output. However, this result is inconclusive due to the low-quality inference of the search output.
  • Recommendation: Please manually verify whether any models in the codebase use custom relation naming conventions that could lead to mismatches. If such cases exist, consider adopting a more direct matching strategy (e.g., using the relation's actual name) to improve the robustness of relation discovery.

@GautierDele
Copy link
Member

This needs to follow the "search" method, to allow includes / selects / etc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants