From e17548e5e76f180d8d2a3d4abbe055addf8fd756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 4 Aug 2018 22:25:55 +0200 Subject: [PATCH] preparing for proposal composition --- app/Http/Controllers/TableController.php | 20 +++-- app/Models/Proposal.php | 60 ++++++++++++++- app/Tables/Changeset.php | 98 +++++++++++++++++++++++- 3 files changed, 167 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/TableController.php b/app/Http/Controllers/TableController.php index 2633bd5..08235e4 100644 --- a/app/Http/Controllers/TableController.php +++ b/app/Http/Controllers/TableController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Models\Revision; use App\Models\Table; use App\Models\User; +use App\Tables\Changeset; use App\Tables\Column; use App\Tables\ColumnNumerator; use App\Tables\CStructArrayExporter; @@ -57,22 +58,29 @@ class TableController extends Controller public function draftChange(Request $request, User $user, string $table) { /** @var Table $tableModel */ - $tableModel = $user->tables()->with('revision')->where('name', $table)->first(); + $tableModel = $user->tables()->where('name', $table)->first(); if ($tableModel === null) abort(404, "No such table."); - $revision = $tableModel->revision; + $session_key = "proposal_{$tableModel->id}"; - $columns = Column::columnsFromJson($revision->columns); + /** @var Changeset $changeset */ + $changeset = $request->session()->remember($session_key, function () use ($tableModel) { + $changeset = new Changeset(); + $changeset->table = $tableModel; + $changeset->revision = $tableModel->revision; + return $changeset; + }); + $revision = $changeset->revision; + $columns = Column::columnsFromJson($revision->columns); $rows = $revision->rowsData($columns)->paginate(25, []); - // TODO instantiate changeset and store it in session - - return view('table.view', [ + return view('table.propose', [ 'table' => $tableModel, 'revision' => $revision, 'columns' => $columns, 'rows' => $rows, + 'changeset' => $changeset, ]); } diff --git a/app/Models/Proposal.php b/app/Models/Proposal.php index f746405..a56f313 100644 --- a/app/Models/Proposal.php +++ b/app/Models/Proposal.php @@ -3,6 +3,8 @@ namespace App\Models; use App\Models\Concerns\Reportable; +use App\Tables\Changeset; +use MightyPork\Exceptions\NotApplicableException; /** * Change proposal @@ -14,7 +16,7 @@ use App\Models\Concerns\Reportable; * @property int $revision_id * @property int $author_id * @property string $note - * @property object $changes - JSONB + * @property Proposal $changes - JSONB * @property-read User $author * @property-read Table $table * @property-read Revision $revision @@ -43,4 +45,60 @@ class Proposal extends BaseModel { return $this->belongsTo(Table::class); } + + public function getChangesAttribute($value) + { + $changeset = Changeset::fromObject(fromJSON($value)); + $changeset->revision = $this->revision; + $changeset->table = $this->table; + $changeset->note = $this->note; + + return $changeset; + } + + public function setChangesAttribute($value) + { + if ($value instanceof Changeset) { + $this->attributes['changes'] = toJSON($value->toObject()); + } else { + throw new NotApplicableException("Only a Changeset may be set to Proposal->changes"); + } + } + + /** + * Create a new Proposal instance wrapping this changeset, + * owned by the currently logged in User. The created instance + * is NOT saved yet. + * + * @param Changeset $changeset - changeset to hydrate the proposal with + * @return Proposal + */ + public static function fromChangeset(Changeset $changeset) + { + if (!$changeset->hasAnyChanges()) { + throw new NotApplicableException('No changes to propose.'); + } + + if ($changeset->note == null) { + throw new NotApplicableException('Proposal note must be filled.'); + } + + if ($changeset->table == null || !$changeset->table instanceof Table) { + throw new NotApplicableException('Table not assigned to Changeset'); + } + + if ($changeset->revision == null || !$changeset->revision instanceof Revision) { + throw new NotApplicableException('Revision not assigned to Changeset'); + } + + return new Proposal([ + // relations + 'table_id' => $changeset->table->getKey(), + 'revision_id' => $changeset->revision->getKey(), + 'author_id' => \Auth::user()->getKey(), + // the proposal info + 'note' => $changeset->note, + 'changes' => $changeset->toObject(), + ]); + } } diff --git a/app/Tables/Changeset.php b/app/Tables/Changeset.php index 39cfe88..2bcde12 100644 --- a/app/Tables/Changeset.php +++ b/app/Tables/Changeset.php @@ -3,15 +3,44 @@ namespace App\Tables; +use App\Models\Revision; +use App\Models\Table; +use Illuminate\Queue\SerializesModels; +use ReflectionClass; + /** * Object representing a set of table modifications */ class Changeset { + use SerializesModels; + + const object_excluded_properties = [ + 'revision', + 'table', + 'note', + ]; + + /** + * @var Revision - base revision this changeset belongs to + */ + public $revision; + /** - * Rows whose content changed + * @var Table - table this changeset belongs to + */ + public $table; + + /** + * @var string - user's note attached to this changeset (future proposal) + */ + public $note; + + /** + * Rows whose content changed, identified by _id. + * Only changed values are to be filled. Columns are identified by GCIDs * - * @var array|null - [GRID -> values, ...] + * @var array|null - [[_id:…, …], …] */ public $rowUpdates; @@ -19,7 +48,7 @@ class Changeset * New rows in the full Row::data format, including GRIDs. * Values are identified by GCIDs from previously defined, or new columns. * - * @var array|null - [[_id:..., ...], [..., ...], ...] + * @var array|null - [[_id:…, …], …] */ public $newRows; @@ -41,7 +70,7 @@ class Changeset /** * New columns in the full format, including GCIDs * - * @var array|null - [[id:..., ...], [..., ...], ...] + * @var array|null - [[id:…, …], …] */ public $newColumns; @@ -66,4 +95,65 @@ class Changeset * @var int[]|null - GCIDs */ public $removedColumns; + + private function walkProps() + { + $properties = (new ReflectionClass($this))->getProperties(); + + foreach ($properties as $property) { + if (in_array($property->name, self::object_excluded_properties)) { + continue; + } + + yield $property->name; + } + } + + /** + * Reconstruct from a object, such as one found in Proposal. + * Note that the fromProposal() method should be used when the + * proposal is available, as it populates additional fields. + * + * @param \stdClass $changes + * @return Changeset + */ + public static function fromObject($changes) + { + $changeset = new Changeset(); + + foreach ($changeset->walkProps() as $prop) { + if (isset($changes->$prop)) { + $changeset->$prop = $changes->$prop; + } + } + + return $changeset; + } + + /** + * Serialize to an object format that may be stored in a Proposal. + * + * @return \stdClass + */ + public function toObject() + { + $object = new \stdClass(); + + foreach ($this->walkProps() as $prop) { + $object->$prop = array_values($this->$prop); + } + + return $object; + } + + public function hasAnyChanges() + { + foreach ($this->walkProps() as $prop) { + if (!empty($this->$prop)) { + return true; + } + } + + return false; + } }