Files
rspade_system/docs/skills/polymorphic/SKILL.md
root 1b46c5270c Add skills documentation and misc updates
Add form value persistence across cache revalidation re-renders

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-29 04:38:06 +00:00

5.2 KiB
Executable File

name, description
name description
polymorphic RSX polymorphic relationships with type references storing integers instead of class names. Use when implementing morphTo relationships, defining type_ref_columns, handling polymorphic form fields, or using polymorphic join helpers.

RSX Polymorphic Relationships

Overview

RSX uses a type reference system that stores integers in the database but transparently converts to/from class name strings in PHP.

$activity->eventable_type = 'Contact_Model';  // Stores integer in DB
echo $activity->eventable_type;               // Returns "Contact_Model"

Benefits:

  • Efficient integer storage (not VARCHAR class names)
  • Automatic type discovery
  • Transparent conversion
  • Laravel morphTo() compatibility

Defining Type Reference Columns

Declare which columns are type references in your model:

class Activity_Model extends Rsx_Model_Abstract
{
    protected static $type_ref_columns = ['eventable_type'];

    public function eventable()
    {
        return $this->morphTo();
    }
}

The cast is automatically applied - no manual $casts needed.


Database Schema

Type reference columns must be BIGINT, not VARCHAR:

CREATE TABLE activities (
    id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    eventable_type BIGINT NULL,
    eventable_id BIGINT NULL,
    action VARCHAR(50) NOT NULL,
    INDEX idx_eventable (eventable_type, eventable_id)
);

Usage

Setting Values

$activity = new Activity_Model();
$activity->eventable_type = 'Contact_Model';  // Use class name
$activity->eventable_id = 123;
$activity->save();

Reading Values

echo $activity->eventable_type;  // "Contact_Model" (string)
$related = $activity->eventable; // Returns Contact_Model instance

Querying

Class names are automatically converted to IDs in WHERE clauses:

// All work - class names auto-converted
Activity_Model::where('eventable_type', 'Contact_Model')->get();
Activity_Model::whereIn('eventable_type', ['Contact_Model', 'Project_Model'])->get();

Polymorphic Join Helpers

Join tables with polymorphic columns:

// INNER JOIN - contacts that have attachments
Contact_Model::query()
    ->joinMorph('file_attachments', 'fileable')
    ->select('contacts.*', 'file_attachments.filename')
    ->get();

// LEFT JOIN - all contacts, with attachments if they exist
Contact_Model::query()
    ->leftJoinMorph('file_attachments', 'fileable')
    ->get();

// RIGHT JOIN
Contact_Model::query()
    ->rightJoinMorph('file_attachments', 'fileable')
    ->get();

Parameters:

  • $table - Table with polymorphic columns (e.g., 'file_attachments')
  • $morphName - Column prefix (e.g., 'fileable' for fileable_type/fileable_id)
  • $morphClass - Optional explicit class (defaults to current model)

Form Handling

Client-Side Format

Polymorphic fields submit as JSON:

eventable={"model":"Contact_Model","id":123}

Server-Side Parsing

use App\RSpade\Core\Polymorphic_Field_Helper;

#[Ajax_Endpoint]
public static function save(Request $request, array $params = [])
{
    $eventable = Polymorphic_Field_Helper::parse($params['eventable'], [
        Contact_Model::class,
        Project_Model::class,
    ]);

    // Validate
    if ($error = $eventable->validate('Please select an entity')) {
        return response_error(Ajax::ERROR_VALIDATION, ['eventable' => $error]);
    }

    // Use
    $activity = new Activity_Model();
    $activity->eventable_type = $eventable->model;  // "Contact_Model"
    $activity->eventable_id = $eventable->id;       // 123
    $activity->save();
}

Important: Always use Model::class for the whitelist.


Auto-Discovery

When storing a new class name that isn't in _type_refs yet:

$attachment->fileable_type = 'Custom_Model';
$attachment->save();

RSX will:

  1. Verify Custom_Model exists and extends Rsx_Model_Abstract
  2. Create a new _type_refs entry with next available ID
  3. Store that ID in the column

Any model can be used without pre-registration.


Common Patterns

File Attachments to Multiple Models

class File_Attachment_Model extends Rsx_Model_Abstract
{
    protected static $type_ref_columns = ['fileable_type'];

    public function fileable()
    {
        return $this->morphTo();
    }
}

// Attach to contact
$attachment->fileable_type = 'Contact_Model';
$attachment->fileable_id = $contact->id;

// Attach to project
$attachment->fileable_type = 'Project_Model';
$attachment->fileable_id = $project->id;

Activity Log

class Activity_Model extends Rsx_Model_Abstract
{
    protected static $type_ref_columns = ['subject_type'];

    public function subject()
    {
        return $this->morphTo();
    }
}

// Log activity for any model
Activity_Model::log('updated', $contact);  // subject_type = 'Contact_Model'
Activity_Model::log('created', $project);  // subject_type = 'Project_Model'

Simple Names Only

Always use simple class names (basename), never FQCNs:

// ✅ Correct
$activity->eventable_type = 'Contact_Model';

// ❌ Wrong - fully qualified
$activity->eventable_type = 'App\\Models\\Contact_Model';

More Information

Details: php artisan rsx:man polymorphic