1
votes

Description

I have a "belongsToMany" eloquent relationship between users and roles . I am creating a CRUD system with capability to do bulk actions. I followed the Livewire Screencasts Bulk Export/Delete and Refactoring For Re-Usability. I have created a trait for the bulk actions, so I'm able to use it out of the box.

I need to detach the role from the user and then delete the user in bulk. I am unable to call roles relationship on a public method property and detach the same. This is how I detach the role for a single user $this->user->roles()->detach(); but I'm unable to do it with $this->selectedRowsQuery->roles()->detach(); in case of bulk user deletion.

Stripped-down, copy-pastable code snippets

  • Livewire/Backend/UserController
public $showUserBulkDeletionModal = false;

public function confirmDeleteBulk()
{
    $deleteCount = $this->selectedRowsQuery->count();
        
    $this->selectedRowsQuery->roles()->detach();
    $this->selectedRowsQuery->delete();
    $this->showUserBulkDeletionModal = false;

    $this->notify('You\'ve deleted '.$deleteCount.' users');
}

public function getRowsQueryProperty()
{
    $query = User::query()
        ->when($this->filters['email'], fn($query, $email) => $query->where('email', 'like', '%'.$email.'%'))
        ->when($this->filters['role'], fn($query, $role) => $query->whereHas('roles', fn ($query) => $query->where('id', $role)))
        ->when($this->filters['search'], fn($query, $search) => $query->where('name', 'like', '%'.$search.'%'))
        ->when($this->filters['date-min'], fn($query, $created_at) => $query->where('created_at', '>=', Carbon::createFromFormat('d/m/Y', $created_at)))
        ->when($this->filters['date-max'], fn($query, $created_at) => $query->where('created_at', '<=', Carbon::createFromFormat('d/m/Y', $created_at)));
        
    return $this->applySorting($query);
}
  • Livewire/Traits/WithBulkActions
trait WithBulkActions
{
    public $selectPage = false;
    public $selectAll = false;
    public $selected = [];

    public function renderingWithBulkActions()
    {
        if ($this->selectAll) $this->selectPageRows();
    }

    public function updatedSelected()
    {
        $this->selectAll = false;
        $this->selectPage = false;
    }

    public function updatedSelectPage($value)
    {
        if ($value) return $this->selectPageRows();

        $this->selectAll = false;
        $this->selected = [];
    }

    public function selectPageRows()
    {
        $this->selected = $this->rows->pluck('id')->map(fn($id) => (string) $id);
    }

    public function selectAll()
    {
        $this->selectAll = true;
    }

    public function getSelectedRowsQueryProperty()
    {
        return (clone $this->rowsQuery)
            ->unless($this->selectAll, fn($query) => $query->whereKey($this->selected));
    }
}

Context

  • Livewire version: 2.3.6
  • Laravel version: 8.23.1
  • Alpine version: 2.8.0
  • Browser: Chrome
1
What does just this code returns $this->selectedRowsQuery->roles()?ByWaleed

1 Answers

2
votes

This line won't work:

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

$this->selectedRowsQuery is a Collection, and your code isn't smart enough to know which instance of roles() you're trying to detach (delete). You simply need to do this in a loop:

foreach ($this->selectedRowsQuery as $queryRow) {
  $queryRow->roles()->detach();
  $queryRow->delete(); // Can do this here (single delete), or below
}

$this->selectedRowsQuery->delete(); // Can do this here (batch delete), or above

Edit: At the time of the foreach(), $this->selectedRowsQuery is still an instance of the Builder class, which is incompatible with foreach() until a Closure (get(), cursor(), etc.) is passed. To handle this, simply adjust your code as:

foreach ($this->selectedRowsQuery->get() as $queryRow) {
  $queryRow->roles()->detach();
  ...
}

Note: ->get() is more widely used, but ->cursor() is available and generally more performant for larger loops.