POLYMORPHIC(7) RSX Framework Manual POLYMORPHIC(7) NAME polymorphic - JSON-encoded polymorphic field handling SYNOPSIS Server-side: use App\RSpade\Core\Polymorphic_Field_Helper; $field = Polymorphic_Field_Helper::parse($params['fieldname'], [ Contact_Model::class, Project_Model::class, ]); if ($error = $field->validate('Please select an entity')) { $errors['fieldname'] = $error; } $model->poly_type = $field->model; $model->poly_id = $field->id; Client-side (form input value format): {"model":"Contact_Model","id":123} DESCRIPTION Polymorphic fields allow a single database relationship to reference multiple model types. For example, an Activity can be related to either a Contact or a Project. This document describes the standard pattern for handling polymorphic fields in RSX applications. The Problem Traditional form handling passes polymorphic data as separate fields: eventable_type=Contact_Model eventable_id=123 This approach has issues: - Requires custom form submission handlers to inject hidden fields - Field naming is inconsistent (type vs _type, id vs _id) - No standard validation pattern - Security validation (allowed model types) is ad-hoc The Solution Polymorphic fields are submitted as a single JSON-encoded value: eventable={"model":"Contact_Model","id":123} Benefits: - Single field to validate - Standard val() getter/setter pattern on client - Polymorphic_Field_Helper handles parsing and security validation - Clean, reusable code on both client and server SERVER-SIDE USAGE Polymorphic_Field_Helper Class Location: App\RSpade\Core\Polymorphic_Field_Helper Parse a field value: use App\RSpade\Core\Polymorphic_Field_Helper; $eventable = Polymorphic_Field_Helper::parse($params['eventable'], [ Contact_Model::class, Project_Model::class, ]); The second argument is the whitelist of allowed model classes. Always use Model::class syntax - never hardcode model name strings. Validation Methods Required field validation: if ($error = $eventable->validate('Please select an entity')) { $errors['eventable'] = $error; } Optional field validation: if ($error = $parent->validate_optional('Invalid parent type')) { $errors['parent'] = $error; } Accessing Values After validation: $model->eventable_type = $eventable->model; // "Contact_Model" $model->eventable_id = $eventable->id; // 123 For optional fields, id will be null if not provided: $model->parent_id = $parent->id; // null or integer State Checking Methods $field->is_empty() // No value provided $field->is_valid() // Value provided and model type allowed $field->is_invalid() // Value provided but model type not allowed CLIENT-SIDE IMPLEMENTATION Building a Polymorphic Picker Component To create a picker for polymorphic fields, build a component that: 1. Maintains a hidden input with the JSON-encoded value 2. Implements val() to get/set as {model: 'Model_Name', id: number} 3. Syncs the hidden input on every change 4. Handles pre-load value setting (value set before options loaded) Hidden Input Pattern The component template should include a hidden input: On value change, sync to hidden input as JSON: _sync_hidden_value() { const $hidden = this.$sid('hidden_value'); if (this._value && this._value.model && this._value.id) { $hidden.val(JSON.stringify(this._value)); } else { $hidden.val(''); } } val() Implementation The val() method should work before and after component loads: val(value) { if (arguments.length === 0) { // Getter return this._value || this._pending_value || null; } // Setter const parsed = value ? {model: value.model, id: parseInt(value.id)} : null; if (this.data.loading) { this._pending_value = parsed; this._value = parsed; this._sync_hidden_value(); } else { this._value = parsed; this._apply_to_ui(); this._sync_hidden_value(); } } Using Tom Select For searchable dropdowns, use Tom Select with a compound value format: // Compound value: "Model_Name:id" valueField: 'compound_value', onChange: function(compound_value) { if (!compound_value) { that._value = null; } else { const parts = compound_value.split(':'); that._value = {model: parts[0], id: parseInt(parts[1])}; } that._sync_hidden_value(); } Add options with the compound format: all_options.push({ compound_value: 'Contact_Model:' + contact.id, model: 'Contact_Model', id: contact.id, name: contact.name, }); COMPLETE EXAMPLE Controller use App\RSpade\Core\Polymorphic_Field_Helper; #[Ajax_Endpoint] public static function save(Request $request, array $params = []) { $eventable = Polymorphic_Field_Helper::parse($params['eventable'] ?? null, [ Contact_Model::class, Project_Model::class, ]); $errors = []; if ($error = $eventable->validate('Please select an entity')) { $errors['eventable'] = $error; } if (!empty($errors)) { return response_form_error('Please correct errors', $errors); } $activity = new Activity_Model(); $activity->eventable_type = $eventable->model; $activity->eventable_id = $eventable->id; $activity->save(); return ['success' => true]; } DATABASE SCHEMA Polymorphic relationships use two columns: $table->string('eventable_type'); $table->unsignedBigInteger('eventable_id'); The type column stores the model class basename (e.g., "Contact_Model"). SECURITY CONSIDERATIONS Model Type Validation CRITICAL: Always validate that submitted model types are in the allowed list. Attackers could submit arbitrary model names to exploit the polymorphic relationship. The Polymorphic_Field_Helper enforces this automatically: // Only Contact_Model and Project_Model are accepted $eventable = Polymorphic_Field_Helper::parse($params['eventable'], [ Contact_Model::class, Project_Model::class, ]); // Submitting {"model":"User_Model","id":1} will fail validation $eventable->is_valid(); // false Use Model::class, Not Strings Always use the ::class constant to specify allowed models: // CORRECT - uses class constant Polymorphic_Field_Helper::parse($value, [Contact_Model::class]); // WRONG - hardcoded string Polymorphic_Field_Helper::parse($value, ['Contact_Model']); Using ::class ensures: - IDE autocompletion and refactoring support - Compile-time error if class doesn't exist - Consistent naming with actual class names SEE ALSO form_conventions(7), ajax_error_handling(7) RSX Framework 2025-12-23 POLYMORPHIC(7)