mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 18:34:04 +00:00
wip
This commit is contained in:
parent
e756b54b31
commit
3e514461ca
5 changed files with 260 additions and 22 deletions
|
|
@ -16,5 +16,5 @@ interface Syncable
|
||||||
|
|
||||||
public function triggerSyncEvent();
|
public function triggerSyncEvent();
|
||||||
|
|
||||||
public function getCreateAttributeNames(): array;
|
public function getResourceCreationAttributes(): array|null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue