creating tables

pull/26/head
Ondřej Hruška 6 years ago
parent 75afc67f31
commit 26488e5883
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 89
      app/Http/Controllers/TableController.php
  2. 2
      app/Models/ContentReport.php
  3. 1
      app/Models/Proposal.php
  4. 3
      app/Models/Revision.php
  5. 1
      app/Models/Row.php
  6. 4
      app/Models/Table.php
  7. 1
      app/Models/TableComment.php
  8. 108
      app/Utils/Column.php
  9. 2
      database/migrations/2018_07_08_193600_create_revisions_table.php
  10. 6
      database/migrations/2018_07_08_193700_create_tables_table.php
  11. 2
      database/migrations/2018_07_08_194000_create_proposals_table.php
  12. 11
      porklib/Exceptions/NotApplicableException.php
  13. 27
      porklib/Utils/Str.php
  14. 2
      porklib/Utils/Utils.php
  15. 2
      resources/views/table/create.blade.php

@ -2,8 +2,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Revision;
use App\Models\Row;
use App\Models\Table; use App\Models\Table;
use App\Models\User;
use App\Utils\Column;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use MightyPork\Exceptions\NotApplicableException;
class TableController extends Controller class TableController extends Controller
{ {
@ -15,14 +20,14 @@ class TableController extends Controller
public function create() public function create()
{ {
$exampleColumns = $exampleColumns =
"latin, string, Latin Name\n". "latin,string,Latin Name\n".
"common, string, Common Name\n". "common,string,Common Name\n".
"lifespan, int, Lifespan (years)"; "lifespan,int,Lifespan (years)";
$exampleData = $exampleData =
"Mercenaria mercenaria, hard clam, 40\n" . "Mercenaria mercenaria,hard clam,40\n" .
"Magallana gigas, pacific oyster, 30\n" . "Magallana gigas,pacific oyster,30\n" .
"Patella vulgata, common limpet, 20"; "Patella vulgata,common limpet,20";
return view('table.create', return view('table.create',
compact('exampleColumns', 'exampleData') compact('exampleColumns', 'exampleData')
@ -31,31 +36,91 @@ class TableController extends Controller
public function storeNew(Request $request) public function storeNew(Request $request)
{ {
/** @var User $u */
$u = \Auth::user(); $u = \Auth::user();
$this->validate($request, [ $this->validate($request, [
'name' => 'required', 'name' => 'required',
'title' => 'string|nullable', 'title' => 'string',
'description' => 'string|nullable', 'description' => 'string|nullable',
'license' => 'string|nullable', 'license' => 'string|nullable',
'upstream' => 'string|nullable', 'origin' => 'string|nullable',
'columns' => 'required', 'columns' => 'required',
'data' => 'string|nullable', 'data' => 'string|nullable',
]); ]);
// Check if table name is unique for user // Check if table name is unique for user
$name = $request->get('name'); $tabName = $request->get('name');
if ($u->tables()->where('name', $name)->exists()) { if ($u->tables()->where('name', $tabName)->exists()) {
return $this->backWithErrors([ return $this->backWithErrors([
'name' => "A table called \"$name\" already exists in your account.", 'name' => "A table called \"$tabName\" already exists in your account.",
]); ]);
} }
// Parse and validate the columns specification // Parse and validate the columns specification
/** @var Column[] $columns */
$columns = [];
$colTable = array_map('str_getcsv', explode("\n", $request->get('columns')));
foreach ($colTable as $col) {
$col = array_map('trim', $col);
if (count($col) < 2) {
return $this->backWithErrors([
'columns' => "All columns must have at least name and type.",
]);
}
// Now we create rows, a revision pointing to them, and the table using it. try {
$columns[] = new Column([
'name' => $col[0],
'type' => $col[1],
'title' => count($col) >= 3 ? $col[2] : $col[0], // title falls back to =name if not specified,
]);
} catch (\Exception $e) {
return $this->backWithErrors(['columns' => $e->getMessage()]);
}
}
$rowTable = array_map('str_getcsv', explode("\n", $request->get('data')));
$rowsData = null;
try {
$rowsData = array_map(function ($row) use ($columns) {
if (count($row) != count($columns)) {
throw new NotApplicableException("All rows must have ".count($columns)." fields.");
}
$parsed = [];
foreach ($row as $i => $val) {
$key = $columns[$i]->name;
$parsed[$key] = $columns[$i]->cast($val);
}
return [
'data' => json_encode($parsed),
'refs' => 1,
];
}, $rowTable);
}catch (\Exception $e) {
return $this->backWithErrors(['columns' => $e->getMessage()]);
}
$revision = Revision::create([
'refs' => 1, // from the new table
'note' => "Initial revision of table $u->name/$tabName",
'columns' => json_encode($columns),
]);
$table = Table::create([
'owner_id' => $u->id,
'revision_id' => $revision->id,
'name' => $tabName,
'title' => $request->get('title'),
'description' => $request->get('description'),
'license' => $request->get('license'),
'origin' => $request->get('origin'),
]);
$revision->rows()->createMany($rowsData);
// Now we create rows, a revision pointing to them, and the table using it.
return "Ok."; return "Ok.";
} }

@ -19,6 +19,8 @@ use Illuminate\Database\Eloquent\Model;
*/ */
class ContentReport extends Model class ContentReport extends Model
{ {
protected $guarded = [];
/** Authoring user */ /** Authoring user */
public function author() public function author()
{ {

@ -23,6 +23,7 @@ use Illuminate\Database\Eloquent\Model;
class Proposal extends Model class Proposal extends Model
{ {
use Reportable; use Reportable;
protected $guarded = [];
protected static function boot() protected static function boot()
{ {

@ -15,7 +15,7 @@ use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships;
* @property int $refs * @property int $refs
* @property int $ancestor_id * @property int $ancestor_id
* @property string $note * @property string $note
* @property string $index_column * @property object $columns
* @property Revision|null $parentRevision * @property Revision|null $parentRevision
* @property Row[]|Collection $rows * @property Row[]|Collection $rows
* @property Proposal|null $sourceProposal - proposal that was used to create this revision * @property Proposal|null $sourceProposal - proposal that was used to create this revision
@ -24,6 +24,7 @@ use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships;
class Revision extends Model class Revision extends Model
{ {
use HasRelaquentRelationships; use HasRelaquentRelationships;
protected $guarded = [];
protected static function boot() protected static function boot()
{ {

@ -13,5 +13,6 @@ use Illuminate\Database\Eloquent\Model;
*/ */
class Row extends Model class Row extends Model
{ {
protected $guarded = [];
public $timestamps = false; public $timestamps = false;
} }

@ -15,10 +15,11 @@ use Illuminate\Database\Eloquent\Model;
* @property int $owner_id * @property int $owner_id
* @property int $ancestor_id * @property int $ancestor_id
* @property int $revision_id * @property int $revision_id
* @property string $name
* @property string $title * @property string $title
* @property string $description * @property string $description
* @property string $license * @property string $license
* @property string $source_link * @property string $origin
* @property User $owner * @property User $owner
* @property Table $parentTable * @property Table $parentTable
* @property Table[]|Collection $forks * @property Table[]|Collection $forks
@ -32,6 +33,7 @@ use Illuminate\Database\Eloquent\Model;
class Table extends Model class Table extends Model
{ {
use Reportable; use Reportable;
protected $guarded = [];
protected static function boot() protected static function boot()
{ {

@ -24,6 +24,7 @@ use Illuminate\Database\Eloquent\Model;
class TableComment extends Model class TableComment extends Model
{ {
use Reportable; use Reportable;
protected $guarded = [];
protected static function boot() protected static function boot()
{ {

@ -0,0 +1,108 @@
<?php
namespace App\Utils;
use JsonSerializable;
use MightyPork\Exceptions\NotApplicableException;
use MightyPork\Utils\Utils;
/**
* Helper class representing one column in a data table.
*
* @property-read string $name
* @property-read string $title
* @property-read string $type
*/
class Column implements JsonSerializable
{
const colTypes = [
'int', 'bool', 'float', 'string'
];
private $name;
private $title;
private $type;
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->$name;
}
throw new NotApplicableException("No such column property");
}
/**
* Create from object or array
*
* @param $obj
*/
public function __construct($obj)
{
$b = new \objBag($obj);
$this->name = $b->name;
$this->title = $b->title;
$this->type = $b->type;
if (!in_array($this->type, self::colTypes)) {
throw new NotApplicableException("\"$this->type\" is not a valid column type.");
}
}
/**
* @return array with keys {name, title, type}
*/
public function toArray()
{
return [
'name' => $this->name,
'title' => $this->title,
'type' => $this->type,
];
}
/**
* Convert a value to the target type, validating it in the process
*
* @param mixed $value
* @return bool|float|int|string
*/
public function cast($value)
{
switch ($this->type) {
case 'int':
if (is_int($value)) return $value;
if (is_float($value)) return round($value);
if (is_numeric($value)) return intval($value);
throw new NotApplicableException("Could not convert value \"$value\" to int!");
case 'float':
if (is_int($value) || is_float($value)) return (float)$value;
if (is_numeric($value)) return floatval($value);
throw new NotApplicableException("Could not convert value \"$value\" to float!");
case 'bool':
return Utils::parseBool($value);
case 'string':
return "$value";
default:
throw new \LogicException("Illegal column type: \"$this->type\"");
}
}
/**
* Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->toArray();
}
}

@ -24,7 +24,7 @@ class CreateRevisionsTable extends Migration
$table->jsonb('columns'); $table->jsonb('columns');
// author's note describing what has been changed // author's note describing what has been changed
$table->text('note'); $table->text('note')->nullable();
$table->foreign('ancestor_id')->references('id')->on('revisions') $table->foreign('ancestor_id')->references('id')->on('revisions')
->onDelete('set null'); ->onDelete('set null');

@ -21,9 +21,9 @@ class CreateTablesTable extends Migration
$table->unsignedInteger('revision_id')->index(); // active revision $table->unsignedInteger('revision_id')->index(); // active revision
$table->string('name')->index(); // indexable $table->string('name')->index(); // indexable
$table->string('title')->index(); // indexable $table->string('title')->index(); // indexable
$table->text('description'); $table->text('description')->nullable();
$table->text('license'); $table->text('license')->nullable();
$table->text('source_link'); $table->text('origin')->nullable();
$table->foreign('owner_id')->references('id')->on('users') $table->foreign('owner_id')->references('id')->on('users')
->onDelete('restrict'); // user deleting their account deletes owned tables ->onDelete('restrict'); // user deleting their account deletes owned tables

@ -23,7 +23,7 @@ class CreateProposalsTable extends Migration
$table->unsignedInteger('revision_id')->index(); // parent revision (applying it to a different revisions may cause conflicts) $table->unsignedInteger('revision_id')->index(); // parent revision (applying it to a different revisions may cause conflicts)
$table->unsignedInteger('author_id')->index(); $table->unsignedInteger('author_id')->index();
$table->text('note'); $table->text('note')->nullable();
$table->jsonb('changes'); // the actual meat of the changeset is stored here $table->jsonb('changes'); // the actual meat of the changeset is stored here

@ -0,0 +1,11 @@
<?php
namespace MightyPork\Exceptions;
/**
* Something can't be used the way it was
*/
class NotApplicableException extends RuntimeException
{
//
}

@ -8,8 +8,6 @@ use Log;
class Str extends \Illuminate\Support\Str class Str extends \Illuminate\Support\Str
{ {
protected static $cty_snake_cache = [];
/** /**
* Split and trim * Split and trim
* *
@ -26,31 +24,6 @@ class Str extends \Illuminate\Support\Str
return array_map('trim', self::split($haystack, $delimiters)); return array_map('trim', self::split($haystack, $delimiters));
} }
/**
* Customized snake case for cty aliases
*
* @param $camel
* @return string
*/
public static function snakeAlias($camel)
{
if (isset(static::$cty_snake_cache[$camel]))
return static::$cty_snake_cache[$camel];
$c = $camel;
$c = preg_replace_callback('/([A-Z0-9]+)(?![a-z])/', function ($m) {
return ucfirst(strtolower($m[1]));
}, $c);
$c = self::snake($c, '_');
// fix for underscores in cty class name
$c = preg_replace('/_+/', '_', $c);
return static::$cty_snake_cache[$camel] = $c;
}
/** /**
* Split a string using one or more delimiters * Split a string using one or more delimiters
* *

@ -52,6 +52,8 @@ class Utils
return $value ? 1 : 0; return $value ? 1 : 0;
} }
if ($value === null) return false;
// convert to string if needed (ie. SimpleXMLElement) // convert to string if needed (ie. SimpleXMLElement)
// small overhead for numbers (negligible) // small overhead for numbers (negligible)
$value = (string) $value; $value = (string) $value;

@ -24,7 +24,7 @@
->help('License applicable to the table\'s data, if any. By default, all ->help('License applicable to the table\'s data, if any. By default, all
tables are CC0 or Public Domain.') !!} tables are CC0 or Public Domain.') !!}
{!! Widget::text('upstream', 'Adapted from') {!! Widget::text('origin', 'Adapted from')
->help('If you took the data from some external site, a book, etc., write it here. ->help('If you took the data from some external site, a book, etc., write it here.
URLs in a full format will be clickable.') !!} URLs in a full format will be clickable.') !!}

Loading…
Cancel
Save