1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-05 15:14:04 +00:00
This commit is contained in:
Abrar Ahmad 2022-08-16 15:27:34 +05:00
parent e756b54b31
commit 3e514461ca
5 changed files with 260 additions and 22 deletions

View file

@ -16,5 +16,5 @@ interface Syncable
public function triggerSyncEvent(); public function triggerSyncEvent();
public function getCreateAttributeNames(): array; public function getResourceCreationAttributes(): array|null;
} }

View file

@ -32,4 +32,9 @@ trait ResourceSyncing
/** @var Syncable $this */ /** @var Syncable $this */
event(new SyncedResourceSaved($this, tenant())); event(new SyncedResourceSaved($this, tenant()));
} }
public function getResourceCreationAttributes(): array|null
{
return null;
}
} }

View file

@ -6,6 +6,8 @@ namespace Stancl\Tenancy\Listeners;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Support\Arr;
use Stancl\Tenancy\Contracts\Syncable;
use Stancl\Tenancy\Contracts\SyncMaster; use Stancl\Tenancy\Contracts\SyncMaster;
use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase; use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase;
use Stancl\Tenancy\Events\SyncedResourceSaved; use Stancl\Tenancy\Events\SyncedResourceSaved;
@ -58,7 +60,7 @@ class UpdateSyncedResource extends QueueableListener
event(new SyncedResourceChangedInForeignDatabase($event->model, null)); event(new SyncedResourceChangedInForeignDatabase($event->model, null));
} else { } else {
// If the resource doesn't exist at all in the central DB,we create // If the resource doesn't exist at all in the central DB,we create
$centralModel = $event->model->getCentralModelName()::create($event->model->only($event->model->getCreateAttributeNames())); $centralModel = $event->model->getCentralModelName()::create($this->getResourceCreationAttributes($event->model));
event(new SyncedResourceChangedInForeignDatabase($event->model, null)); event(new SyncedResourceChangedInForeignDatabase($event->model, null));
} }
}); });
@ -111,11 +113,24 @@ class UpdateSyncedResource extends QueueableListener
$localModel->update($syncedAttributes); $localModel->update($syncedAttributes);
} else { } else {
// When creating, we use all columns, not just the synced ones. // When creating, we use all columns, not just the synced ones.
$localModel = $localModelClass::create($eventModel->getAttributes()); $localModel = $localModelClass::create($this->getResourceCreationAttributes($eventModel));
} }
event(new SyncedResourceChangedInForeignDatabase($localModel, $tenant)); event(new SyncedResourceChangedInForeignDatabase($localModel, $tenant));
}); });
}); });
} }
function getResourceCreationAttributes(Syncable $model): array
{
$attributes = $model->getAttributes();
if ($model->getResourceCreationAttributes()) {
// If array is key-value, We assume default values are provided
// if array is plain values, fetch attributes from model
$attributes = Arr::isAssoc($model->getResourceCreationAttributes()) ? $model->getResourceCreationAttributes() : $model->only($model->getResourceCreationAttributes());
}
return $attributes;
}
} }

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddExtraColumnToUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('foo');
});
}
public function down()
{
}
}

View file

@ -47,6 +47,7 @@ beforeEach(function () {
UpdateSyncedResource::$shouldQueue = false; // global state cleanup UpdateSyncedResource::$shouldQueue = false; // global state cleanup
Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class); Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class);
// run migrations on central connection
pest()->artisan('migrate', [ pest()->artisan('migrate', [
'--path' => [ '--path' => [
__DIR__ . '/Etc/synced_resource_migrations', __DIR__ . '/Etc/synced_resource_migrations',
@ -126,10 +127,96 @@ test('only the synced columns are updated in the central db', function () {
], ResourceUser::first()->getAttributes()); ], ResourceUser::first()->getAttributes());
}); });
test('creating the resource in tenant database creates it in central database with provided attribute', function () { test('creating the resource in tenant database creates it in central database ad 1:1 copy when create attributes are null', function () {
// Assert no user exists in central DB // Assert no user exists in central DB
expect(ResourceUser::all())->toHaveCount(0); expect(ResourceUser::all())->toHaveCount(0);
$tenant = ResourceTenant::create();
migrateTenantsResource();
tenancy()->initialize($tenant);
// Create the user in tenant DB
ResourceUser::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // unsynced
]);
tenancy()->end();
// Assert central user was created without `code` property
expect(CentralUser::first()->toArray())->toEqual(ResourceUser::first()->toArray());
});
test('creating the resource in tenant database creates it in central database with provided default attributes values', function () {
// override method in ResourceUser class to return attribute values
class ResourceUserWithDefaultValues extends ResourceUser {
public function getResourceCreationAttributes(): array
{
// Attributes default values when creating resources from tenant to central DB
return
[
'global_id' => 'abc-123',
'name' => 'John',
'password' => 'password',
'email' => 'john@demo',
'role' => 'admin',
];
}
}
// Assert no user exists in central DB
expect(ResourceUserWithDefaultValues::all())->toHaveCount(0);
$tenant = ResourceTenant::create();
migrateTenantsResource();
tenancy()->initialize($tenant);
// Create the user in tenant DB
ResourceUserWithDefaultValues::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // unsynced
]);
tenancy()->end();
expect(CentralUser::first()->global_id)->toBe('abc-123');
expect(CentralUser::first()->name)->toBe('John');
expect(CentralUser::first()->password)->toBe('password');
expect(CentralUser::first()->email)->toBe('john@demo');
expect(CentralUser::first()->role)->toBe('admin');
});
test('creating the resource in tenant database creates it in central database with provided attributes names', function () {
// override method in ResourceUser class to return attribute names
class ResourceUserWithAttributeNames extends ResourceUser {
public function getResourceCreationAttributes(): array
{
// Attributes used when creating resources from tenant to central DB
// Notice here we are not adding "code" filed because it does
// not exist in central model
return
[
'global_id',
'name',
'password',
'email',
'role'
];
}
}
// Assert no user exists in central DB
expect(ResourceUserWithAttributeNames::all())->toHaveCount(0);
$tenant = ResourceTenant::create(); $tenant = ResourceTenant::create();
pest()->artisan('tenants:migrate', [ pest()->artisan('tenants:migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/custom', '--path' => __DIR__ . '/Etc/synced_resource_migrations/custom',
@ -139,7 +226,7 @@ test('creating the resource in tenant database creates it in central database wi
tenancy()->initialize($tenant); tenancy()->initialize($tenant);
// Create the user in tenant DB // Create the user in tenant DB
ResourceUser::create([ ResourceUserWithAttributeNames::create([
'global_id' => 'acme', 'global_id' => 'acme',
'name' => 'John Doe', 'name' => 'John Doe',
'email' => 'john@localhost', 'email' => 'john@localhost',
@ -155,6 +242,128 @@ test('creating the resource in tenant database creates it in central database wi
expect(CentralUser::first()->code)->toBeNull(); expect(CentralUser::first()->code)->toBeNull();
}); });
test('creating the resource in central database creates it in tenant database with 1:1 copy when create attributes are null', function () {
$centralUser = CentralUser::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // unsynced
]);
$tenant = ResourceTenant::create([
'id' => 't1',
]);
migrateTenantsResource();
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(1);
expect(ResourceUser::first()->global_id)->toBe('acme');
});
});
test('creating the resource in central database creates it in tenant database with provided default attributes values', function () {
// override method in ResourceUser class to return attribute values
class CentralUserWithDefaultValues extends CentralUser {
public function getResourceCreationAttributes(): array
{
// Attributes default values when creating resources from central to tenant model
return
[
'global_id' => 'abc-123',
'name' => 'John',
'password' => 'password',
'email' => 'john@demo',
'role' => 'admin',
];
}
}
$centralUser = CentralUserWithDefaultValues::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter', // unsynced
]);
$tenant = ResourceTenant::create([
'id' => 't1',
]);
migrateTenantsResource();
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(1);
expect(ResourceUser::first()->global_id)->toBe('abc-123');
expect(ResourceUser::first()->name)->toBe('John');
expect(ResourceUser::first()->password)->toBe('password');
expect(ResourceUser::first()->email)->toBe('john@demo');
expect(ResourceUser::first()->role)->toBe('admin');
});
});
test('creating the resource in central database creates it in tenant database with provided attributes names', function () {
// override method in ResourceUser class to return attribute values
class CentralUserWithAttributeNames extends CentralUser {
public function getResourceCreationAttributes(): array
{
// Attributes used when creating resources from central to tenant DB
return
[
'global_id',
'name',
'password',
'email',
'role',
];
}
}
// migrate extra column in central DB
pest()->artisan('migrate', [
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra',
'--realpath' => true,
])->assertExitCode(0);
$centralUser = CentralUserWithAttributeNames::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
'password' => 'secret',
'role' => 'commenter',
'foo' => 'bar', // foo does not exist in resource
]);
$tenant = ResourceTenant::create([
'id' => 't1',
]);
migrateTenantsResource();
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(0);
});
$centralUser->tenants()->attach('t1');
$tenant->run(function () {
expect(ResourceUser::all())->toHaveCount(1);
expect(ResourceUser::first()->global_id)->toBe('acme');
expect(ResourceUser::first()->foo)->toBeNull(); // assert foo is not copied from the central to tenant model
});
});
test('creating the resource in tenant database creates it in central database and creates the mapping', function () { test('creating the resource in tenant database creates it in central database and creates the mapping', function () {
creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase(); creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase();
}); });
@ -627,11 +836,6 @@ class CentralUser extends Model implements SyncMaster
'email', 'email',
]; ];
} }
public function getCreateAttributeNames(): array
{
return [];
}
} }
class ResourceUser extends Model implements Syncable class ResourceUser extends Model implements Syncable
@ -667,16 +871,4 @@ class ResourceUser extends Model implements Syncable
'email', 'email',
]; ];
} }
public function getCreateAttributeNames(): array
{
// Attributes used when creating resources from tenant to central DB
return [
'global_id',
'name',
'password',
'email',
'role'
];
}
} }