Use temporary negative IDs for rows created in a draft Changeset

This commit is contained in:
2018-08-11 16:19:32 +02:00
parent 7938519a64
commit 8efc31d820
12 changed files with 193 additions and 72 deletions
+1 -1
View File
@@ -213,7 +213,7 @@ class TableController extends Controller
$rowsToInsert = null;
$rowNumerator = null;
try {
$rowsToInsert = Changeset::csvToRowsArray($columns, $dataCsvLines, true)->all();
$rowsToInsert = (new Changeset)->csvToRowsArray($columns, $dataCsvLines, true, false)->all();
} catch (\Exception $e) {
return $this->backWithErrors(['data' => $e->getMessage()]);
}
@@ -334,6 +334,23 @@ class TableEditController extends Controller
*/
public function draftSubmit(Request $request, User $user, string $table)
{
/** @var Table $tableModel */
$tableModel = $user->tables()->where('name', $table)->first();
if ($tableModel === null) abort(404, "No such table.");
$input = $this->validate($request, [
'note' => 'required|string'
]);
$changeset = $this->getChangeset($tableModel);
$changeset->note = trim($input->note);
if (empty($changeset->note)) throw new SimpleValidationException('note', 'Note is required.');
if (!$changeset->getRowChangeCounts()->any && !$changeset->getColumnChangeCounts()->any) {
flash()->error("There are no changes to submit.");
return back();
}
//
}
}
+4 -1
View File
@@ -79,7 +79,7 @@ class Proposal extends BaseModel
throw new NotApplicableException('No changes to propose.');
}
if ($changeset->note == null) {
if (empty($changeset->note)) {
throw new NotApplicableException('Proposal note must be filled.');
}
@@ -91,6 +91,9 @@ class Proposal extends BaseModel
throw new NotApplicableException('Revision not assigned to Changeset');
}
// Assign unique row IDs to new rows (they use temporary negative IDs in a draft)
$changeset->renumberRows();
return new Proposal([
// relations
'table_id' => $changeset->table->getKey(),
+12 -2
View File
@@ -3,7 +3,9 @@
namespace App\Tables;
/**
* Sequential ID assigner
*/
abstract class BaseNumerator
{
/** @var int */
@@ -39,10 +41,18 @@ abstract class BaseNumerator
throw new \OutOfBoundsException("Column numerator has run out of allocated GCID slots");
$key = $this->getKey($this->next);
$this->next++;
$this->advance();
return $key;
}
/**
* Advance to the next ID
*/
protected function advance()
{
$this->next++;
}
/**
* Convert numeric index to a key
*
+92 -24
View File
@@ -104,6 +104,14 @@ class Changeset
*/
public $removedColumns = [];
/**
* Draft row numerator state holder; numbers grow to negative,
* and are replaced with real unique row IDs when the proposal is submitted.
*
* @var int
*/
public $nextRowID = -1;
/**
* Generator iterating all properties, used internally for serialization to array
*
@@ -160,22 +168,6 @@ class Changeset
return $object;
}
/**
* Check if there is any change in this changeset
*
* @return bool - any found
*/
public function hasAnyChanges()
{
foreach ($this->walkProps() as $prop) {
if (!empty($this->$prop)) {
return true;
}
}
return false;
}
/**
* Decorate / transform a single row for the editor view
*
@@ -504,6 +496,9 @@ class Changeset
}
}
// try creating a column with the new data
new Column(array_merge($col->toArray(), $updateObj));
if ($this->isNewColumn($id)) {
$this->newColumns[$id] = array_merge($this->newColumns[$id], $updateObj);
}
@@ -569,7 +564,7 @@ class Changeset
* so it can be inserted directly into DB
* @return Collection
*/
public static function csvToRowsArray($columns, $csvArray, $forTableInsert)
public function csvToRowsArray($columns, $csvArray, $forTableInsert, $useDraftIds)
{
/** @var Collection $rows */
$rows = collect($csvArray)->map(function ($row) use ($columns, $forTableInsert) {
@@ -597,9 +592,11 @@ class Changeset
}
})->filter();
// TODO we could use some temporary IDs and replace them with
// TODO proper unique IDs when submitting the proposal
$rowNumerator = new RowNumerator(count($csvArray));
if ($useDraftIds) {
$rowNumerator = new DraftRowNumerator($this, count($csvArray));
} else {
$rowNumerator = new RowNumerator(count($csvArray));
}
if ($forTableInsert) {
return $rows->map(function ($row) use (&$rowNumerator) {
@@ -628,9 +625,7 @@ class Changeset
$template[$column->id] = $column->cast(null);
}
// TODO we could use some temporary IDs and replace them with
// TODO proper unique IDs when submitting the proposal
$numerator = new RowNumerator($count);
$numerator = new DraftRowNumerator($this, $count);
foreach ($numerator->generate() as $_id) {
$row = $template;
@@ -649,7 +644,7 @@ class Changeset
/** @var Column[] $columns */
$columns = array_values($this->fetchAndTransformColumns());
$rows = self::csvToRowsArray($columns, $csvArray, false)
$rows = self::csvToRowsArray($columns, $csvArray, false, true)
->keyBy('_id');
// using '+' to avoid renumbering
@@ -803,4 +798,77 @@ class Changeset
{
$this->rowUpdates = [];
}
/**
* Get row change counts (used for the view)
*
* @return object
*/
public function getRowChangeCounts()
{
$numChangedRows = count($this->rowUpdates);
$numNewRows = count($this->newRows);
$numRemovedRows = count($this->removedRows);
return (object) [
'changed' => $numChangedRows,
'new' => $numNewRows,
'removed' => $numRemovedRows,
'any' => $numChangedRows || $numNewRows || $numRemovedRows,
];
}
/**
* Get column change counts (used for the view)
*
* @return object
*/
public function getColumnChangeCounts()
{
$numChangedColumns = count($this->columnUpdates);
$numNewColumns = count($this->newColumns);
$numRemovedColumns = count($this->removedColumns);
$reordered = count($this->columnOrder) != 0;
return (object) [
'changed' => $numChangedColumns,
'new' => $numNewColumns,
'removed' => $numRemovedColumns,
'reordered' => $reordered,
'any' => $reordered || $numChangedColumns || $numNewColumns || $numRemovedColumns,
];
}
/**
* Check if there is any change in this changeset
*
* @return bool - any found
*/
public function hasAnyChanges()
{
$colChanges = $this->getColumnChangeCounts();
$rowChanges = $this->getRowChangeCounts();
return $colChanges->any || $rowChanges->any;
}
/**
* Replace temporary negative row IDs with real unique row IDs
*/
public function renumberRows()
{
$rows = [];
$numerator = new RowNumerator(count($this->newRows));
foreach ($this->newRows as $row) {
if ($row['_id'] < 0) {
$id = $numerator->next();
$row['_id'] = $id;
$rows[$id] = $row;
} else {
// keep ID
$rows[$row['_id']] = $row;
}
}
$this->newRows = $rows;
}
}
+10 -10
View File
@@ -125,27 +125,27 @@ class Column implements JsonSerializable, Arrayable
*
* @param object|array $obj
*/
public function __construct($obj, $allowEmptyName=false)
public function __construct($obj)
{
$b = objBag($obj);
if (!$allowEmptyName && (!$b->has('name') || $b->name == '')) {
throw new NotApplicableException('Missing name in column');
if (!$b->has('name') || $b->name == '') {
throw new SimpleValidationException('name', "Missing column name.");
}
if (!$b->has('type')) {
throw new NotApplicableException('Missing type in column');
if (!$b->has('type') || !in_array($b->type, self::colTypes)) {
throw new SimpleValidationException('type', "Missing or invalid column type.");
}
if ($b->name[0] == '_') { // would cause problems with selects (see: _id / GRID)
throw new NotApplicableException("Column name can't start with underscore.");
if ($b->name[0] == '_' || !preg_match(IDENTIFIER_PATTERN, $b->name)) {
throw new SimpleValidationException('name', "Column name can contain only letters, digits, and underscore, and must start with a letter.");
}
if (!in_array($b->type, self::colTypes)) {
throw new NotApplicableException("\"$b->type\" is not a valid column type.");
if (!$b->has('id') || $b->id[0] == '_') {
throw new SimpleValidationException('id', "Invalid or missing column ID");
}
$this->id = $b->get('id', null);
$this->id = $b->id;
$this->name = $b->name;
$this->type = $b->type;
$this->title = $b->title ?: $b->name;
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Tables;
/**
* Utility for allocating & assigning temporary row IDs
* for rows in a changeset
*/
class DraftRowNumerator extends BaseNumerator
{
/**
* Create a numerator for the given number of rows.
*
* @param int $capacity - how many
*/
public function __construct(Changeset $changeset, $capacity)
{
parent::__construct([$changeset->nextRowID - $capacity + 1, $changeset->nextRowID]);
$changeset->nextRowID -= $capacity;
}
}
+3
View File
@@ -5,6 +5,9 @@ namespace App\Tables;
use App\Models\Row;
/**
* Utility for allocating & assigning globally unique row IDs
*/
class RowNumerator extends BaseNumerator
{
/**