Laravel many to many: check only one relationship of same category before attaching

Viewed 30

in my laravel app I have a many to many relationhip between user and badges, where an user can only select a badge of a given category (can't have more than one badge of the same category).

Users will select one badge of each given badge category from a selectable.

So what I want to do is check if there is a badge relationship belonging to the selected category, if there is not then I attach the id, if already one exists, I detach current one and then attach new one.

Migrations:

public function users()
    {
        return $this->belongsToMany('App\Models\User', 'user_badge', 'badge_id', 'user_id');
    }
public function badges()
    {
        return $this->belongsToMany('App\Models\Badge', 'user_badge', 'user_id', 'badge_id');
    }

Schema::create('badges', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('badgecategory_id')->index();
            $table->foreign('badgecategory_id')->references('id')->on('badgecategories')->onDelete('cascade')->onUpdate('cascade');
            $table->string('title');
            $table->string('icon')->nullable();
            $table->timestamps();
        });

Schema::create('user_badge', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id')->index()->nullable();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');
            $table->unsignedInteger('badge_id')->nullable()->index();
            $table->foreign('badge_id')->references('id')->on('badges')->onDelete('cascade')->onUpdate('cascade');
            $table->timestamps();
        });

Now some pseudo code

$hasBadgeId = $badge = where badge category is givenCategoryId

if($hasBadgeId)
{
    $user->badges()->dettach($hasBadgeId);
    $user->badges()->attach($badgeId);
}
else
{
    $user->badges()->attach($badgeId);
}
1 Answers

I think you've got a couple approaches that can handle this, but they both involve querying the ->badges() relationship of the current $user:

  1. Check for a single Badge Association for the given Category ID:
$badge = Badge::find(1); // Replace 1 with whatever Badge ID you want to Attach

// If you're only expecting a Single Badge for a given Category (optimistic case):
$existingBadge = $user->badges()->where('badgecategory_id', $badge->badgecategory_id)->first();

if ($existingBadge) {
  $user->badges()->detach($existingBadge->id);
}

// Finally, Attach the new Badge
$user->badges()->attach($badge->id);
  1. Query for all Badge Associations for the given Category ID:
// If there's a chance there's already multiple Badges for the same Category (pessimistic case)
$existingBadgeIds = $user->badges()->where('badgecategory_id', $categoryId)->pluck('id');

if (!$existingBadgeIds->empty()) {
  $user->badges()->detach($existingBadgeIds->toArray());
}

// Finally, Attach the new Badge
$user->badges()->attach($badge->id);

As you can see, you can check for a single Badge for the given Category and detach it, or query for multiple, and detach them. The Multiple approach will work for 1-∞, so it's actually the safer approach here, in case you have existing bad data in there, but they should both work

Related