spatie/laravel-permission

Relation model_has_roles not found error on Role deletion

Ivan3008 opened this issue · 13 comments

My application uses two database connections. First connection is for general database and users table (tenants), second is for tenant databases.
After upgrading to laravel-permission 6.x i started getting errors when deleting roles, as HasPermissions trait now detaches users from Role model on force deletion.
Undefined table: 7 ERROR: relation "model_has_roles" does not exist

The problem is that Role model uses tenant connection but User model uses central connection , therefore i can't call methods like sync, detach or attach.

Isn't calling detach inside boot method of HasPermissions trait too strict? i'd like to handle data deletion in more flexible way

Commit where detach was added to boot
25cb3a1#diff-414318d551d45206e27857bc76671901b48cab8630221188f4160c44e8b2cadaR41

Versions
spatie/laravel-permission 6.1
PHP version: 8.2
Laravel 10
Database version: postgresql 14

it could work if i had limited number of databases and known database names. but tenants have database names based on id in UUID format

Ok, add a custom Role/Permission model, and overwrite users relation, set the real db name on the pivot tables(model_has_permissions, model_has_roles)
https://stackoverflow.com/questions/25142968/belongstomany-relationship-in-laravel-across-multiple-databases

public function users(): BelongsToMany
{
return $this->morphedByMany(
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
'model',
config('permission.table_names.model_has_permissions'),
app(PermissionRegistrar::class)->pivotPermission,
config('permission.column_names.model_morph_key')
);
}

public function users(): BelongsToMany
{
return $this->morphedByMany(
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
'model',
config('permission.table_names.model_has_roles'),
app(PermissionRegistrar::class)->pivotRole,
config('permission.column_names.model_morph_key')
);
}

unfortunately it didn't work. also, i tried method setConnection(CONNECTION_NAME) for users relationship, it didn't help either

Undefined table: 7 ERROR: relation "tenant_bcc6d892-a4db-40db-b271-fcc6eaaa1276.model_has_roles" does not exist
Connection: pgsql, SQL: delete from "tenant_bcc6d892-a4db-40db-b271-fcc6eaaa1276"."model_has_roles" where "tenant_bcc6d892-a4db-40db-b271-fcc6eaaa1276"."model_has_roles"."role_id" = 5

is tenant_bcc6d892-a4db-40db-b271-fcc6eaaa1276 the connetion name or the db name??
I don't know how it works in postgres, in mysql it works without problems

tenant_bcc6d892-a4db-40db-b271-fcc6eaaa1276 is db name.

$connection property for User is 'pgsql', $connection property for Role is 'tenant'

I don't have any experience with postgres, sorry

I don't know if it will be possible to override the boot methods, try that and remove the lines that give you problems
Another option would be to use softDeletes trait (which I prefer), so those lines would never be called

Question:
can you use the scopes functionality?
because it uses relationships in the same way as users

https://spatie.be/laravel-permission/basic-usage#content-scopes
https://laravel.com/eloquent-relationships#defining-custom-intermediate-table-models

i'll try to overwrite boot method of the trait, if laravel allows overwrite such type of methods

thank you @parallels999 for help, finally i overwrote bootHasPermissions method and error's gone. other possible solutions didn't work out in my situation.

anyway, overwriting boot method is not the perfect solution, because any time i'll update app dependencies, i'll need to check if any important changes in HasPermissions boot method were added, and copy them to my boot

also, schemas of model_has_roles and model_has_permissions tables use onDelete('cascade') method, that do the same as detach. that's why i'm not sure we really need to do the same on deletion

use onDelete('cascade') method, that do the same as detach

Normally softDelete is used, it's not the same

that's right, i missed it