diff --git a/src/Database/Concerns/ResourceSyncing.php b/src/Database/Concerns/ResourceSyncing.php index ea9f83b4..97f8e020 100644 --- a/src/Database/Concerns/ResourceSyncing.php +++ b/src/Database/Concerns/ResourceSyncing.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Stancl\Tenancy\Contracts\Syncable; use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator; +use Stancl\Tenancy\Database\Models\Tenant; +use Stancl\Tenancy\Database\Models\TenantPivot; use Stancl\Tenancy\Events\SyncedResourceSaved; trait ResourceSyncing @@ -43,4 +46,15 @@ trait ResourceSyncing { return true; } + + public function resources(): MorphToMany + { + return $this->morphToMany($this->getResourceTenantModelName(), 'tenant_resources', 'tenant_resources', 'resource_global_id', 'tenant_id', 'global_id') + ->using(TenantPivot::class); + } + + public function getResourceTenantModelName(): string // todo better name + { + return config('tenancy.tenant_model', Tenant::class); + } } diff --git a/tests/ResourceSyncingPolymorphicTest.php b/tests/ResourceSyncingPolymorphicTest.php index 085fbfe3..e6efda9a 100644 --- a/tests/ResourceSyncingPolymorphicTest.php +++ b/tests/ResourceSyncingPolymorphicTest.php @@ -73,16 +73,9 @@ test('polymorphic relationship works for every model when syncing resources from // When central model provides nothing/null, the resource model will be created as a 1:1 copy of central model $centralUser->resources()->attach('t1'); - expect($centralUser->getSyncedCreationAttributes())->toBeNull(); $tenant1->run(function () use ($centralUser) { - $resourceUser = ResourceUserForPolymorphic::first(); - expect($resourceUser)->not()->toBeNull(); - $resourceUser = $resourceUser->toArray(); - $centralUser = $centralUser->withoutRelations()->toArray(); - - // remove id from comparison, because we don't copy id and let target model handle it - unset($resourceUser['id']); - unset($centralUser['id']); + $resourceUser = ResourceUserForPolymorphic::first()->only(['name', 'email', 'password', 'role']); + $centralUser = $centralUser->only(['name', 'email', 'password', 'role']); expect($resourceUser)->toBe($centralUser); }); @@ -104,16 +97,9 @@ test('polymorphic relationship works for every model when syncing resources from // When central model provides nothing/null, the resource model will be created as a 1:1 copy of central model $centralCompany->resources()->attach('t2'); - expect($centralCompany->getSyncedCreationAttributes())->toBeNull(); $tenant2->run(function () use ($centralCompany) { - $resourceCompany = ResourceCompanyForPolymorphic::first(); - expect($resourceCompany)->not()->toBeNull(); - $resourceCompany = $resourceCompany->toArray(); - $centralCompany = $centralCompany->withoutRelations()->toArray(); - - // remove id from comparison, because we don't copy id and let target model handle it - unset($resourceCompany['id']); - unset($centralCompany['id']); + $resourceCompany = ResourceCompanyForPolymorphic::first()->only(['name', 'email']); + $centralCompany = $centralCompany->only(['name', 'email']); expect($resourceCompany)->toBe($centralCompany); }); @@ -186,13 +172,13 @@ function migrateCompaniesTableForTenants(): void class ResourceTenantForPolymorphic extends Tenant { - public function users() + public function users(): MorphToMany { return $this->morphedByMany(CentralUserForPolymorphic::class, 'tenant_resources', 'tenant_resources', 'tenant_id', 'resource_global_id', 'id', 'global_id') ->using(TenantPivot::class); } - public function companies() + public function companies(): MorphToMany { return $this->morphedByMany(CentralCompanyForPolymorphic::class, 'tenant_resources', 'tenant_resources', 'tenant_id', 'resource_global_id', 'id', 'global_id') ->using(TenantPivot::class); @@ -209,10 +195,10 @@ class CentralUserForPolymorphic extends Model implements SyncMaster public $table = 'users'; - public function resources(): MorphToMany + // override method to provide different tenant + public function getResourceTenantModelName(): string { - return $this->morphToMany(ResourceTenantForPolymorphic::class, 'tenant_resources', 'tenant_resources', 'resource_global_id', 'tenant_id', 'global_id') - ->using(TenantPivot::class); + return ResourceTenantForPolymorphic::class; } public function getTenantModelName(): string @@ -292,10 +278,10 @@ class CentralCompanyForPolymorphic extends Model implements SyncMaster public $table = 'companies'; - public function resources(): MorphToMany + // override method to provide different tenant + public function getResourceTenantModelName(): string { - return $this->morphToMany(ResourceTenantForPolymorphic::class, 'tenant_resources', 'tenant_resources', 'resource_global_id', 'tenant_id', 'global_id') - ->using(TenantPivot::class); + return ResourceTenantForPolymorphic::class; } public function getTenantModelName(): string diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php index e1586bc1..c547f853 100644 --- a/tests/ResourceSyncingTest.php +++ b/tests/ResourceSyncingTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Events\CallQueuedListener; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Queue; @@ -149,7 +150,7 @@ test('sync resource creation works when central model provides attributes and re }); // When central model provides the list of attributes, resource model will be created from the provided list of attributes' values - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $tenant1->run(function () { $resourceUser = ResourceUserProvidingDefaultValues::all(); @@ -205,7 +206,7 @@ test('sync resource creation works when central model provides default values an }); // When central model provides the list of default values, resource model will be created from the provided list of default values - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $tenant1->run(function () { // Assert resource user was created using the list of default values @@ -257,7 +258,7 @@ test('sync resource creation works when central model provides mixture and resou }); // When central model provides the list of a mixture (attributes and default values), resource model will be created from the provided list of mixture (attributes and default values) - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $tenant1->run(function () { $resourceUser = ResourceUser::first(); @@ -284,14 +285,10 @@ test('sync resource creation works when central model provides mixture and resou tenancy()->end(); - $centralUser = CentralUserProvidingMixture::whereGlobalId('acmey')->first(); + $centralUser = CentralUserProvidingMixture::whereGlobalId('acmey')->first()->only(['name', 'email', 'password', 'role']); expect($resourceUser->getSyncedCreationAttributes())->toBeNull(); - $centralUser = $centralUser->toArray(); - $resourceUser = $resourceUser->toArray(); - unset($centralUser['id']); - unset($resourceUser['id']); - + $resourceUser = $resourceUser->only(['name', 'email', 'password', 'role']); // Assert central user created as 1:1 copy of resource model except "id" expect($centralUser)->toBe($resourceUser); }); @@ -315,16 +312,12 @@ test('sync resource creation works when central model provides nothing and resou }); // When central model provides nothing/null, the resource model will be created as a 1:1 copy of central model - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); expect($centralUser->getSyncedCreationAttributes())->toBeNull(); $tenant1->run(function () use ($centralUser) { - $resourceUser = ResourceUserProvidingMixture::first(); - expect($resourceUser)->not()->toBeNull(); - $resourceUser = $resourceUser->toArray(); - $centralUser = $centralUser->withoutRelations()->toArray(); - unset($resourceUser['id']); - unset($centralUser['id']); + $resourceUser = ResourceUserProvidingMixture::first()->only(['name', 'email', 'password', 'role']); + $centralUser = $centralUser->only(['name', 'email', 'password', 'role']); expect($resourceUser)->toBe($centralUser); }); @@ -384,7 +377,7 @@ test('attaching a tenant to the central resource triggers a pull from the tenant expect(ResourceUser::all())->toHaveCount(0); }); - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $tenant->run(function () { expect(ResourceUser::all())->toHaveCount(1); @@ -440,8 +433,8 @@ test('resources are synced only to workspaces that have the resource', function ]); migrateUsersTableForTenants(); - $centralUser->tenants()->attach('t1'); - $centralUser->tenants()->attach('t2'); + $centralUser->resources()->attach('t1'); + $centralUser->resources()->attach('t2'); // t3 is not attached $t1->run(function () { @@ -479,7 +472,7 @@ test('when a resource exists in other tenant dbs but is created in a tenant db t migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $t2->run(function () { // Create user with the same global ID in t2 database @@ -527,9 +520,9 @@ test('the synced columns are updated in other tenant dbs where the resource exis migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB - $centralUser->tenants()->attach('t1'); - $centralUser->tenants()->attach('t2'); - $centralUser->tenants()->attach('t3'); + $centralUser->resources()->attach('t1'); + $centralUser->resources()->attach('t2'); + $centralUser->resources()->attach('t3'); $t3->run(function () { ResourceUser::first()->update([ @@ -581,7 +574,7 @@ test('when the resource doesnt exist in the tenant db non synced columns will ca migrateUsersTableForTenants(); - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); $t1->run(function () { expect(ResourceUser::first()->role)->toBe('employee'); @@ -657,17 +650,17 @@ test('an event is fired for all touched resources', function () { migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB - $centralUser->tenants()->attach('t1'); + $centralUser->resources()->attach('t1'); Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) { return $event->tenant->getTenantKey() === 't1'; }); - $centralUser->tenants()->attach('t2'); + $centralUser->resources()->attach('t2'); Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) { return $event->tenant->getTenantKey() === 't2'; }); - $centralUser->tenants()->attach('t3'); + $centralUser->resources()->attach('t3'); Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) { return $event->tenant->getTenantKey() === 't3'; }); @@ -755,7 +748,7 @@ function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase() expect(CentralUser::first()->role)->toBe('commenter'); // Assert mapping was created - expect(CentralUser::first()->tenants)->toHaveCount(1); + expect(CentralUser::first()->resources)->toHaveCount(1); // Assert role change doesn't cascade CentralUser::first()->update(['role' => 'central superadmin']); @@ -792,7 +785,7 @@ test('resources are synced only when sync is enabled', function (bool $enabled) 'role' => 'commenter', ]); - $centralUser->tenants()->attach('t2'); + $centralUser->resources()->attach('t2'); $tenant2->run(function () use ($enabled) { expect(TenantUserWithConditionalSync::all())->toHaveCount($enabled ? 1 : 0); @@ -831,9 +824,9 @@ function migrateUsersTableForTenants(): void class ResourceTenant extends Tenant { - public function users() + public function users(): MorphToMany { - return $this->belongsToMany(CentralUser::class, 'tenant_users', 'tenant_id', 'global_user_id', 'id', 'global_id') + return $this->morphedByMany(CentralUserForPolymorphic::class, 'tenant_resources', 'tenant_resources', 'tenant_id', 'resource_global_id', 'id', 'global_id') ->using(TenantPivot::class); } } @@ -848,10 +841,10 @@ class CentralUser extends Model implements SyncMaster public $table = 'users'; - public function tenants(): BelongsToMany + // override method to provide different tenant + public function getResourceTenantModelName(): string { - return $this->belongsToMany(ResourceTenant::class, 'tenant_users', 'global_user_id', 'tenant_id', 'global_id') - ->using(TenantPivot::class); + return ResourceTenant::class; } public function getTenantModelName(): string