From 3641ac5bd232cd2937d8933ab853352a22e3dba4 Mon Sep 17 00:00:00 2001 From: distinctm Date: Wed, 12 Jun 2019 17:25:47 -0400 Subject: [PATCH] Support for ordered sync/importing - Order specified in config - Tests for ordered sync - New ErrorUpdatingModelException specifies which Model the error was thrown on --- config/data-sync.php | 3 ++ readme.md | 14 ++++- .../ErrorUpdatingModelException.php | 16 ++++++ src/Updater.php | 53 +++++++++++++++---- tests/Unit/UpdaterTest.php | 30 ++++++++++- tests/test-data/ordered/roles.json | 9 ++++ tests/test-data/ordered/supervisor.json | 5 ++ 7 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 src/Exceptions/ErrorUpdatingModelException.php create mode 100644 tests/test-data/ordered/roles.json create mode 100644 tests/test-data/ordered/supervisor.json diff --git a/config/data-sync.php b/config/data-sync.php index 9b52b0a..15ce8b9 100644 --- a/config/data-sync.php +++ b/config/data-sync.php @@ -2,4 +2,7 @@ return [ 'path' => base_path('sync'), + 'order' => [ + // + ] ]; diff --git a/readme.md b/readme.md index a6b9ceb..3c94f88 100644 --- a/readme.md +++ b/readme.md @@ -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 - 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 data:sync` +- Run `php artisan data:sync` (or `php artisan data:sync --model={model}` with the model flag to specify a model) ### Optional 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 - 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 -- 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 +- order of import can be set in _config/data-sync.php_ with an array: +``` +return [ + 'path' => base_path('sync'), + 'order' => [ + 'Role', + 'Supervisor', + ] +]; +``` ## Examples ### User.json: diff --git a/src/Exceptions/ErrorUpdatingModelException.php b/src/Exceptions/ErrorUpdatingModelException.php new file mode 100644 index 0000000..c974a89 --- /dev/null +++ b/src/Exceptions/ErrorUpdatingModelException.php @@ -0,0 +1,16 @@ +message = "Error updating the {$message} model."; + } +} \ No newline at end of file diff --git a/src/Updater.php b/src/Updater.php index e83ad4e..eee9664 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -7,6 +7,7 @@ use distinctm\LaravelDataSync\Exceptions\NoCriteriaException; use distinctm\LaravelDataSync\Exceptions\NoRecordsInvalidJSONException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\File; +use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; class Updater { @@ -31,11 +32,17 @@ class Updater */ public function run() { - $records = collect($this->files)->map(function ($file) { - return $this->syncModel($file); - }); + $files = $this->sortModels($this->files); - 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 * - * @return array + * @return string * @throws \distinctm\LaravelDataSync\Exceptions\FileDirectoryNotFoundException */ protected function getDirectory($path) @@ -91,17 +98,41 @@ class Updater * @param string $directory * @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) { - return $directory . '/' . $model . '.json'; + return Collection::wrap($directory . '/' . $model . '.json'); } return collect(File::files($directory))->map(function ($path) { 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 * - * @return array + * @return \Illuminate\Support\Collection * @throws \distinctm\LaravelDataSync\Exceptions\NoCriteriaException */ protected function getCriteria(object $record) @@ -132,7 +163,7 @@ class Updater * * @param object $record * - * @return array + * @return \Illuminate\Support\Collection */ protected function getValues(object $record) { diff --git a/tests/Unit/UpdaterTest.php b/tests/Unit/UpdaterTest.php index 74cdcab..7a611f2 100644 --- a/tests/Unit/UpdaterTest.php +++ b/tests/Unit/UpdaterTest.php @@ -4,6 +4,7 @@ namespace distinctm\LaravelDataSync\Tests; use distinctm\LaravelDataSync\Tests\Fakes\UpdaterFake; use Exception; +use distinctm\LaravelDataSync\Exceptions\ErrorUpdatingModelException; class UpdaterTest extends TestCase { @@ -86,7 +87,6 @@ class UpdaterTest extends TestCase } catch (Exception $e) { $this->assertContains('No records or invalid JSON for', $e->getMessage()); } - } /** @test */ @@ -101,6 +101,34 @@ class UpdaterTest extends TestCase } catch (Exception $e) { $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(); } } \ No newline at end of file diff --git a/tests/test-data/ordered/roles.json b/tests/test-data/ordered/roles.json new file mode 100644 index 0000000..b1b0c7b --- /dev/null +++ b/tests/test-data/ordered/roles.json @@ -0,0 +1,9 @@ +[ + { + "_slug": "update-student-records", + "category": "testing", + "supervisor": { + "name": "CEO" + } + } +] \ No newline at end of file diff --git a/tests/test-data/ordered/supervisor.json b/tests/test-data/ordered/supervisor.json new file mode 100644 index 0000000..12bd2e8 --- /dev/null +++ b/tests/test-data/ordered/supervisor.json @@ -0,0 +1,5 @@ +[ + { + "_name": "CEO" + } +] \ No newline at end of file