Skip to content

Improve category delete process#94

Open
theihasan wants to merge 2 commits intomainfrom
improve-category-delete
Open

Improve category delete process#94
theihasan wants to merge 2 commits intomainfrom
improve-category-delete

Conversation

@theihasan
Copy link
Copy Markdown
Owner

No description provided.

Detach related countries before deletion and block removal if a category has
associated jobs. Throw an exception that reports the number of linked jobs and
instructs to reassign or delete those jobs first.
@theihasan theihasan self-assigned this Dec 17, 2025
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @theihasan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the integrity of the job category deletion process. It introduces pre-deletion checks to ensure that job categories with active job listings cannot be removed, preventing accidental data loss and orphaned records. Additionally, it automates the detachment of associated country records, streamlining the cleanup process. These changes are accompanied by comprehensive feature tests to validate the new behavior and ensure robustness.

Highlights

  • Deletion Logic Enhancement: Modified the DeleteAction and DeleteBulkAction for JobCategory to include a before hook, ensuring pre-deletion validation and cleanup.
  • Relationship Detachment: The before hook now automatically detaches associated countries from a JobCategory before deletion, preventing orphaned pivot table entries.
  • Deletion Prevention: Implemented a check within the before hook that prevents the deletion of a JobCategory if it has active JobListings, throwing an exception to inform the user and maintain data integrity.
  • New Feature Tests: Added tests/Feature/JobCategoryResourceTest.php to cover the new deletion logic, including tests for proper relationship handling and the prevention of deletion when job listings are present.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request improves the category deletion process by adding checks to prevent the deletion of categories with associated job listings and by cleaning up related country data. While this is a good improvement, the current implementation has some critical issues. The order of operations in both single and bulk delete actions can lead to data inconsistency, and the bulk delete action is not atomic. Additionally, the new test to verify this behavior doesn't correctly assert the failure condition. My review provides specific suggestions to address these high-severity issues. For better long-term maintainability, I also recommend moving this business logic into the JobCategory model using observers or model events, which would centralize the rules and ensure they are applied consistently throughout the application.

Comment on lines +109 to +115
->before(function ($record) {
$record->countries()->detach();
$jobCount = $record->jobs()->count();
if ($jobCount > 0) {
throw new \Exception("Cannot delete job category '{$record->name}' because it has {$jobCount} associated job listings. Please reassign or delete those jobs first.");
}
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The detach() call is performed before checking for associated jobs. If the check fails and an exception is thrown, the countries will have already been detached, leading to inconsistent data. The validation should be performed before any data modification to ensure the operation is atomic.

                    ->before(function ($record) {
                        $jobCount = $record->jobs()->count();
                        if ($jobCount > 0) {
                            throw new \Exception("Cannot delete job category '{$record->name}' because it has {$jobCount} associated job listings. Please reassign or delete those jobs first.");
                        }
                        $record->countries()->detach();
                    }),

Comment on lines +120 to +129
->before(function ($records) {
foreach ($records as $record) {
$record->countries()->detach();

$jobCount = $record->jobs()->count();
if ($jobCount > 0) {
throw new \Exception("Cannot delete job category '{$record->name}' because it has {$jobCount} associated job listings. Please reassign or delete those jobs first.");
}
}
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The bulk delete action has two critical issues:

  1. Incorrect logic order: Similar to the single delete action, detach() is called before validation. This can lead to data inconsistency if the deletion is aborted.
  2. Not atomic: The loop processes records one by one. If a record in the middle of the list fails validation, the preceding records will have already had their countries detached, leaving the data in an inconsistent state.

A safer approach is to first loop through all records to validate them, and then, if all validations pass, loop again to perform the detachments. This ensures the operation is more atomic and prevents partial data changes.

                        ->before(function ($records) {
                            foreach ($records as $record) {
                                $jobCount = $record->jobs()->count();
                                if ($jobCount > 0) {
                                    throw new \Exception("Cannot delete job category '{$record->name}' because it has {$jobCount} associated job listings. Please reassign or delete those jobs first.");
                                }
                            }
                            foreach ($records as $record) {
                                $record->countries()->detach();
                            }
                        }),

Comment on lines +48 to +68
public function test_job_category_with_job_listings_cannot_be_deleted(): void
{
// Create a job category
$jobCategory = JobCategory::factory()->create([
'name' => 'Category with Jobs',
]);

// Create job listings associated with this category
JobListing::factory()->count(3)->create([
'job_category' => $jobCategory->id,
]);

// Check if there are job listings
$jobCount = $jobCategory->jobs()->count();

// Verify we have job listings
$this->assertEquals(3, $jobCount);

// This simulates the validation logic from our DeleteAction
$this->assertTrue($jobCount > 0);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

This test is named test_job_category_with_job_listings_cannot_be_deleted, but it doesn't actually assert that the deletion fails or that an exception is thrown. It only verifies the precondition ($jobCount > 0). To make the test effective, it should assert that the expected exception is thrown when the validation logic is executed.

    public function test_job_category_with_job_listings_cannot_be_deleted(): void
    {
        // Create a job category
        $jobCategory = JobCategory::factory()->create([
            'name' => 'Category with Jobs',
        ]);

        // Create job listings associated with this category
        JobListing::factory()->count(3)->create([
            'job_category' => $jobCategory->id,
        ]);

        // Expect an exception to be thrown when simulating the delete validation
        $this->expectException(\Exception::class);
        $this->expectExceptionMessage("Cannot delete job category '{$jobCategory->name}' because it has 3 associated job listings. Please reassign or delete those jobs first.");

        // Simulate the validation logic from the DeleteAction's `before` hook
        $jobCount = $jobCategory->jobs()->count();
        if ($jobCount > 0) {
            throw new \Exception("Cannot delete job category '{$jobCategory->name}' because it has {$jobCount} associated job listings. Please reassign or delete those jobs first.");
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant