Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

use Fleetbase\Models\CustomField;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*
* Custom fields were addressed by uuid alone, which left every API that
* hands one out exposing an internal identifier where the rest of the
* platform shows a public id. Existing rows are backfilled so nothing has
* to cope with a field that has no id.
*/
public function up(): void
{
if (!Schema::hasTable('custom_fields') || Schema::hasColumn('custom_fields', 'public_id')) {
return;
}

Schema::table('custom_fields', function (Blueprint $table) {
$table->string('public_id', 191)->nullable()->after('uuid')->index();
});

CustomField::withTrashed()->whereNull('public_id')->get()->each(function (CustomField $field) {
$field->update(['public_id' => CustomField::generatePublicId('custom_field')]);
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
if (!Schema::hasTable('custom_fields') || !Schema::hasColumn('custom_fields', 'public_id')) {
return;
}

Schema::table('custom_fields', function (Blueprint $table) {
$table->dropIndex(['public_id']);
$table->dropColumn(['public_id']);
});
}
};
11 changes: 10 additions & 1 deletion src/Models/CustomField.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
use Fleetbase\Casts\Json;
use Fleetbase\Casts\PolymorphicType;
use Fleetbase\Traits\HasApiModelBehavior;
use Fleetbase\Traits\HasPublicId;
use Fleetbase\Traits\HasUuid;

class CustomField extends Model
{
use HasUuid;
use HasPublicId;
use HasApiModelBehavior;

/**
Expand All @@ -19,12 +21,19 @@ class CustomField extends Model
*/
protected $table = 'custom_fields';

/**
* The type of public Id to generate.
*
* @var string
*/
protected $publicIdType = 'custom_field';

/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['company_uuid', 'category_uuid', 'subject_uuid', 'subject_type', 'name', 'label', 'type', 'for', 'component', 'options', 'required', 'editable', 'default_value', 'validation_rules', 'meta', 'description', 'help_text', 'order'];
protected $fillable = ['public_id', 'company_uuid', 'category_uuid', 'subject_uuid', 'subject_type', 'name', 'label', 'type', 'for', 'component', 'options', 'required', 'editable', 'default_value', 'validation_rules', 'meta', 'description', 'help_text', 'order'];

/**
* The attributes that are guarded.
Expand Down
7 changes: 6 additions & 1 deletion src/Traits/HasCustomFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,12 @@ public function setCustomFieldValue(string|CustomField $fieldOrKey, mixed $value
'subject_uuid' => $this->getAttribute('uuid'),
'company_uuid' => $this->getAttribute('company_uuid') ?? session('company'),
]);
$field->forceFill(['uuid' => CustomField::generateUuid()]);
// A quiet save skips the `creating` hook that mints a public id,
// so a field created on the fly would be the only one without one.
$field->forceFill([
'uuid' => CustomField::generateUuid(),
'public_id' => CustomField::generatePublicId('custom_field'),
]);
method_exists($field, 'saveQuietly') ? $field->saveQuietly() : $field->save();
// bust definition cache for subsequent lookups
$this->customFieldCache = [];
Expand Down
39 changes: 39 additions & 0 deletions tests/Unit/Models/RecordModelsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,30 @@ public function clear(): bool
$table->timestamps();
$table->softDeletes();
});
$schema->create('custom_fields', function ($table) {
$table->string('uuid')->primary();
$table->string('public_id')->nullable()->unique();
$table->string('company_uuid')->nullable();
$table->string('category_uuid')->nullable();
$table->string('subject_uuid')->nullable();
$table->string('subject_type')->nullable();
$table->string('name')->nullable();
$table->string('label')->nullable();
$table->string('type')->nullable();
$table->string('for')->nullable();
$table->string('component')->nullable();
$table->text('options')->nullable();
$table->boolean('required')->default(false);
$table->boolean('editable')->default(true);
$table->text('default_value')->nullable();
$table->text('validation_rules')->nullable();
$table->text('meta')->nullable();
$table->text('description')->nullable();
$table->text('help_text')->nullable();
$table->integer('order')->default(0);
$table->timestamps();
$table->softDeletes();
});
$schema->create('user_devices', function ($table) {
$table->string('uuid')->primary();
$table->string('public_id')->nullable()->unique();
Expand Down Expand Up @@ -249,6 +273,21 @@ public function clear(): bool
]);
});

it('generates custom field public ids', function () {
record_models_database();

$field = CustomField::query()->create([
'company_uuid' => 'company-1',
'name' => 'brakes',
'label' => 'Brakes',
'type' => 'pass-fail',
]);

expect($field->public_id)->toStartWith('custom_field_')
->and($field->public_id)->toHaveLength(strlen('custom_field_') + 10)
->and($field->uuid)->not->toBeNull();
});

it('casts custom field configuration values and keeps relationship keys stable', function () {
record_models_database();

Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Traits/HasCustomFieldsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ function has_custom_fields_database(string $routeUri = 'int/v1/subjects'): HasCu
});
$schema->create('custom_fields', function ($table) {
$table->string('uuid')->primary();
$table->string('public_id')->nullable();
$table->string('company_uuid')->nullable();
$table->string('category_uuid')->nullable();
$table->string('subject_uuid')->nullable();
Expand Down
Loading