Support for ordered sync/importing

- Order specified in config
- Tests for ordered sync
- New ErrorUpdatingModelException specifies which Model the error was thrown on
This commit is contained in:
distinctm
2019-06-12 17:25:47 -04:00
parent 692e70ef70
commit 3641ac5bd2
7 changed files with 116 additions and 14 deletions

View File

@@ -2,4 +2,7 @@
return [ return [
'path' => base_path('sync'), 'path' => base_path('sync'),
'order' => [
//
]
]; ];

View File

@@ -6,7 +6,7 @@ Laravel utility to keep records synced between enviroments through source contro
- Create a JSON file for each model, using the model name as the filename. Example: Product.json would update the Product model - Create a JSON file for each model, using the model name as the filename. Example: Product.json would update the Product model
- Use nested arrays in place of hardcoded IDs for relationships - Use nested arrays in place of hardcoded IDs for relationships
- Run `php artisan vendor:publish --provider="distinctm\LaravelDataSync\DataSyncBaseServiceProvider" --tag="data-sync-config"` to publish config file. Specify directory for sync data files (default is a new sync directory in the project root) - Run `php artisan vendor:publish --provider="distinctm\LaravelDataSync\DataSyncBaseServiceProvider" --tag="data-sync-config"` to publish config file. Specify directory for sync data files (default is a new sync directory in the project root)
- Run `php artisan data:sync` - Run `php artisan data:sync` (or `php artisan data:sync --model={model}` with the model flag to specify a model)
### Optional ### Optional
If using Laravel Forge, you can have the data sync run automatically on deploy. Edit your deploy script in Site -> App to include: If using Laravel Forge, you can have the data sync run automatically on deploy. Edit your deploy script in Site -> App to include:
@@ -21,8 +21,18 @@ fi
## Notes ## Notes
- use studly case for model name relationships as JSON keys (example: 'option_group' => 'OptionGroup'). This is important for case sensitive file systems. - use studly case for model name relationships as JSON keys (example: 'option_group' => 'OptionGroup'). This is important for case sensitive file systems.
- empty values are skipped - empty values are skipped
- the criteria/attributes for updateOrCreate are identified with a preleading underscore - the criteria/attributes for updateOrCreate are identified with a leading underscore
- nested values represent relationships and are returned using where($key, $value)->first()->id - nested values represent relationships and are returned using where($key, $value)->first()->id
- order of import can be set in _config/data-sync.php_ with an array:
```
return [
'path' => base_path('sync'),
'order' => [
'Role',
'Supervisor',
]
];
```
## Examples ## Examples
### User.json: ### User.json:

View File

@@ -0,0 +1,16 @@
<?php
namespace distinctm\LaravelDataSync\Exceptions;
use Exception;
use Throwable;
class ErrorUpdatingModelException extends Exception
{
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->message = "Error updating the {$message} model.";
}
}

View File

@@ -7,6 +7,7 @@ use distinctm\LaravelDataSync\Exceptions\NoCriteriaException;
use distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException; use distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException;
class Updater class Updater
{ {
@@ -31,11 +32,17 @@ class Updater
*/ */
public function run() public function run()
{ {
$records = collect($this->files)->map(function ($file) { $files = $this->sortModels($this->files);
return $this->syncModel($file);
});
return $records; return $files->map(function ($file) {
try {
return $this->syncModel($file);
} catch (\ErrorException $e) {
$model = pathinfo($file, PATHINFO_FILENAME);
throw new ErrorUpdatingModelException(ucwords($model));
}
});
} }
/** /**
@@ -71,7 +78,7 @@ class Updater
* *
* @param $path * @param $path
* *
* @return array * @return string
* @throws \distinctm\LaravelDataSync\Exceptions\FileDirectoryNotFoundException * @throws \distinctm\LaravelDataSync\Exceptions\FileDirectoryNotFoundException
*/ */
protected function getDirectory($path) protected function getDirectory($path)
@@ -91,17 +98,41 @@ class Updater
* @param string $directory * @param string $directory
* @param string|null $model * @param string|null $model
* *
* @return array|string * @return \Illuminate\Support\Collection
*/ */
protected function getFiles(string $directory, $model) protected function getFiles(string $directory, $model = null)
{ {
if ($model) { if ($model) {
return $directory . '/' . $model . '.json'; return Collection::wrap($directory . '/' . $model . '.json');
} }
return collect(File::files($directory))->map(function ($path) { return collect(File::files($directory))->map(function ($path) {
return $path->getPathname(); return $path->getPathname();
})->toArray(); });
}
/**
* Sort Models by pre-configured order
*
* @param \Illuminate\Support\Collection $files
* @return \Illuminate\Support\Collection
*/
protected function sortModels(\Illuminate\Support\Collection $files)
{
if(empty(config('data-sync.order'))) {
return $files;
}
return $files->sortBy(function($file) use ($files) {
$filename = pathinfo($file, PATHINFO_FILENAME);
$order = array_search(
studly_case($filename),
config('data-sync.order')
);
return $order !== false ? $order : (count($files) + 1);
});
} }
/** /**
@@ -109,7 +140,7 @@ class Updater
* *
* @param object $record * @param object $record
* *
* @return array * @return \Illuminate\Support\Collection
* @throws \distinctm\LaravelDataSync\Exceptions\NoCriteriaException * @throws \distinctm\LaravelDataSync\Exceptions\NoCriteriaException
*/ */
protected function getCriteria(object $record) protected function getCriteria(object $record)
@@ -132,7 +163,7 @@ class Updater
* *
* @param object $record * @param object $record
* *
* @return array * @return \Illuminate\Support\Collection
*/ */
protected function getValues(object $record) protected function getValues(object $record)
{ {

View File

@@ -4,6 +4,7 @@ namespace distinctm\LaravelDataSync\Tests;
use distinctm\LaravelDataSync\Tests\Fakes\UpdaterFake; use distinctm\LaravelDataSync\Tests\Fakes\UpdaterFake;
use Exception; use Exception;
use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException;
class UpdaterTest extends TestCase class UpdaterTest extends TestCase
{ {
@@ -86,7 +87,6 @@ class UpdaterTest extends TestCase
} catch (Exception $e) { } catch (Exception $e) {
$this->assertContains('No records or invalid JSON for', $e->getMessage()); $this->assertContains('No records or invalid JSON for', $e->getMessage());
} }
} }
/** @test */ /** @test */
@@ -101,6 +101,34 @@ class UpdaterTest extends TestCase
} catch (Exception $e) { } catch (Exception $e) {
$this->assertEquals('No criteria/attributes detected', $e->getMessage()); $this->assertEquals('No criteria/attributes detected', $e->getMessage());
} }
}
/** @test */
public function order_of_imports_can_be_defined_in_config()
{
config()->set('data-sync.order', [
'Supervisor',
'Roles'
]);
$updater = new UpdaterFake(__DIR__ . '/../test-data/ordered');
$updater->run();
$this->assertDatabaseHas('roles', ['slug' => 'update-student-records']);
$this->assertDatabaseHas('supervisors', ['name' => 'CEO']);
}
/** @test */
public function exception_is_thrown_if_imports_are_in_incorrect_order()
{
config()->set('data-sync.order', [
'Roles',
'Supervisor'
]);
$this->expectException(ErrorUpdatingModelException::class);
$updater = new UpdaterFake(__DIR__ . '/../test-data/ordered');
$updater->run();
} }
} }

View File

@@ -0,0 +1,9 @@
[
{
"_slug": "update-student-records",
"category": "testing",
"supervisor": {
"name": "CEO"
}
}
]

View File

@@ -0,0 +1,5 @@
[
{
"_name": "CEO"
}
]