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

237 lines
5.2 KiB
Markdown
Executable File

---
name: polymorphic
description: 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.
```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:
```php
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:
```sql
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
```php
$activity = new Activity_Model();
$activity->eventable_type = 'Contact_Model'; // Use class name
$activity->eventable_id = 123;
$activity->save();
```
### Reading Values
```php
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:
```php
// 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:
```php
// 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:
```javascript
eventable={"model":"Contact_Model","id":123}
```
### Server-Side Parsing
```php
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:
```php
$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
```php
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
```php
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:
```php
// ✅ Correct
$activity->eventable_type = 'Contact_Model';
// ❌ Wrong - fully qualified
$activity->eventable_type = 'App\\Models\\Contact_Model';
```
## More Information
Details: `php artisan rsx:man polymorphic`