Skip to content
Advertisement

Laravel – Sync only a subset of the pivot table

My pivot table contains a total of 3 columns:

  • user_id
  • role_id
  • group

Group is just an integer. I would like to be able to sync the users and their roles but only the ones that belong to a specific group.

If I run a simple sync([1,2,3]) it will remove everything from the pivot table, ignoring the group altogether.

I have a few solutions in mind:

Option a:

  1. Create a new model for UserRoles.
  2. UserRoles::where('group', '=', '1');
  3. User::roles()->detach(list_of_ids_from_previous_query);
  4. User::roles()->attach(list_of_desired_ids_for_group_1);

Option b:

  1. User::roles()->all();
  2. Fancy merge $list_of_desired_ids_for_group_1 with $list_of_ids_from_previous_query
  3. User::roles()->sync(list_of_merged_ids);

Is there another way to do this with Eloquent? I reckon option (a) is easier to implement as I don’t have to merge 2 multidimensional arrays of IDs and groups. But also, option (a) might be more database intensive as it needs to run a DELETE and INSERT on all group rows.

Advertisement

Answer

I ended up mimicking the Laravel sync() method but added some additional filtering. I added the method to my Repository, but it could be added as a method to a Model.

If you want to move the method to a model, you could do something like this:

/**
 * Simulates the behaviour of Eloquent sync() but
 * only on a specific subset of the pivot
 * @param  integer $group
 * @param  array  $roles
 * @return Model
 */
public function syncBy($group, array $roles)
{
    // $this is the User model for example
    $current = $this->roles->filter(function($role) use ($group) {
        return $role->pivot->group === $group;
    })->pluck('id');

    $detach = $current->diff($roles)->all();

    $attach_ids = collect($roles)->diff($current)->all();
    $atach_pivot = array_fill(0, count($attach_ids), ['group' => $group]);
    $attach = array_combine($attach_ids, $atach_pivot);

    $this->roles()->detach($detach);
    $this->roles()->attach($attach);

    return $this;
}

Usage:

$user= AppUser::find(1);
// Will sync for user 1, the roles 5, 6, 9 but only within group 3
$user->syncBy(3, [5, 6, 9]);
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement