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>
This commit is contained in:
236
docs/skills/polymorphic/SKILL.md
Executable file
236
docs/skills/polymorphic/SKILL.md
Executable file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
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`
|
||||
Reference in New Issue
Block a user