Files
rspade_system/app/RSpade/man/model.txt
root 84ca3dfe42 Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages
Replace #[Auth] attributes with manual auth checks and code quality rule
Remove on_jqhtml_ready lifecycle method from framework
Complete ACL system with 100-based role indexing and /dev/acl tester
WIP: ACL system implementation with debug instrumentation
Convert rsx:check JS linting to RPC socket server
Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature
Reorganize wishlists: priority order, mark sublayouts complete, add email
Update model_fetch docs: mark MVP complete, fix enum docs, reorganize
Comprehensive documentation overhaul: clarity, compression, and critical rules
Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null()
Add JS ORM relationship lazy-loading and fetch array handling
Add JS ORM relationship fetching and CRUD documentation
Fix ORM hydration and add IDE resolution for Base_* model stubs
Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework
Enhance JS ORM infrastructure and add Json_Tree class name badges

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 21:39:43 +00:00

362 lines
12 KiB
Plaintext
Executable File

RSpade Models (Rsx_Model_Abstract)
OVERVIEW
All RSX models must extend Rsx_Model_Abstract, which provides:
- Enum system with magic properties and methods
- Mass assignment prevention (explicit field assignment only)
- Eager loading prevention (explicit queries only)
- Ajax ORM fetch() system
- Automatic boolean casting for TINYINT(1) columns
- Relationship management via #[Relationship] attribute
ENUM SYSTEM
Define enums using public static $enums property:
public static $enums = [
'status_id' => [
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active', 'order' => 1],
2 => ['constant' => 'STATUS_INACTIVE', 'label' => 'Inactive', 'order' => 2]
],
'role_id' => [
1 => ['constant' => 'ROLE_ADMIN', 'label' => 'Administrator'],
2 => ['constant' => 'ROLE_USER', 'label' => 'User']
]
];
Magic Properties (Instance):
- $model->status_id_label - Get label for current enum value
- $model->status_id_constant - Get constant name for current value
- $model->status_id_order - Get sort order for current value
- $model->status_id_enum_val - Get all properties for current value
Magic Methods (Static):
- Model::status_id_enum() - Get all enum definitions (sorted by 'order')
- Model::status_id_enum_select() - Get key/label pairs for dropdowns
- Model::status_id_enum_ids() - Get all possible enum values
Enum properties in $enums can include any custom keys (label, order, constant,
color, icon, etc.). All properties are exported to JavaScript via toArray().
Optional 'selectable' property (default true) controls dropdown visibility:
2 => ['constant' => 'STATUS_ARCHIVED', 'label' => 'Archived', 'selectable' => false]
MASS ASSIGNMENT PREVENTION
Mass assignment is explicitly prohibited. Use explicit field assignment:
// CORRECT
$model = new User_Model();
$model->name = $request->input('name');
$model->email = $request->input('email');
$model->save();
// WRONG - throws MassAssignmentException
$model = User_Model::create(['name' => 'John', 'email' => 'john@example.com']);
$model->fill(['name' => 'John']);
$model->update(['name' => 'John']);
Blocked methods: fill(), forceFill(), create(), firstOrCreate(), firstOrNew(),
updateOrCreate(), update()
EAGER LOADING PREVENTION
All forms of eager loading throw exceptions. Use explicit queries:
// WRONG - throws exception
$users = User_Model::with('posts')->get();
$user->load('posts');
// CORRECT - explicit queries
$users = User_Model::all();
foreach ($users as $user) {
$posts = Post_Model::where('user_id', $user->id)->get();
}
Rationale: Prevents recursive loading chains that accidentally cascade across
the entire database. Future optimization will use caching strategies instead.
See: php artisan rsx:man rspade (DATABASE PHILOSOPHY section)
AJAX ORM (Model Fetch System)
Models can opt-in to client-side fetching by implementing fetch() with
#[Ajax_Endpoint_Model_Fetch] attribute:
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
// Check authorization
if (!RsxAuth::check()) {
return false;
}
// Fetch single record
$model = static::find($id);
return $model ?: false;
}
JavaScript usage:
const product = await Product_Model.fetch(1);
console.log(product.status_label); // Enum properties populated
console.log(Product_Model.STATUS_ACTIVE); // Static enum constants
Returns instantiated JS model class with enum properties and optional custom
methods. See: php artisan rsx:man model_fetch
RELATIONSHIPS
Define relationships using #[Relationship] attribute to prevent Laravel from
auto-detecting non-relationship methods:
#[Relationship]
public function posts()
{
return $this->hasMany(Post_Model::class);
}
Only methods with #[Relationship] are considered relationships. This prevents
methods like is_active() from being mistaken for relationships.
Note: Eager loading is still blocked. Relationships are supported but must be
loaded with explicit queries.
AUTOMATIC BOOLEAN CASTING
TINYINT(1) columns are automatically cast to boolean without manual $casts
definition. Framework consults manifest database metadata to detect boolean
columns at runtime.
Manual $casts entries take precedence over automatic detection.
NEVER EXPORT FIELDS
Exclude sensitive fields from toArray() and Ajax responses:
protected $neverExport = ['password', 'api_token', 'remember_token'];
Fields in $neverExport are automatically removed before JavaScript export.
UTILITY METHODS
Static Methods:
- get_table_static() - Get table name without instantiating model
- getColumns() - Get array of column names for model's table
- hasColumn($column) - Check if column exists
- get_relationships() - Get array of relationship method names
- clear_table_cache() - Clear query cache for table
- clear_all_caches() - Clear all query caches
Instance Methods:
- make_new() - Create new model instance (recommended over new Model())
- get_mass_assignment_example() - Get helpful example code for this model
MODEL ORGANIZATION WITH TRAITS
Large models can be split into domain-specific trait files. This organization
strategy is primarily designed for models, though traits can be used elsewhere
in RSX applications.
Naming Convention:
Trait files should be prefixed with the model name they elaborate on:
Users_Model -> Users_Model_Authentication (trait for auth methods)
Users_Model -> Users_Model_Relationships (trait for relationship methods)
Users_Model -> Users_Model_Permissions (trait for permission logic)
This makes it immediately clear which model a trait belongs to.
Organization Strategy:
Group methods by problem domain within separate trait files:
- Authentication/authorization logic
- Query methods for specific use cases
- Relationship definitions
- Business logic for specific features
- Computed properties and accessors
Example Implementation:
// rsx/models/traits/users_model_authentication.php
trait Users_Model_Authentication
{
public static function find_by_email(string $email)
{
return static::where('email', $email)->first();
}
public static function find_by_api_token(string $token)
{
return static::where('api_token', $token)->first();
}
public function verify_password(string $password): bool
{
return password_verify($password, $this->password);
}
}
// rsx/models/traits/users_model_relationships.php
trait Users_Model_Relationships
{
#[Relationship]
public function posts()
{
return $this->hasMany(Posts_Model::class);
}
#[Relationship]
public function profile()
{
return $this->hasOne(User_Profiles_Model::class);
}
}
// rsx/models/users_model.php
class Users_Model extends Rsx_Model_Abstract
{
use Users_Model_Authentication;
use Users_Model_Relationships;
protected $table = 'users';
public static $enums;
protected $neverExport = ['password', 'api_token'];
}
Manifest Integration:
The manifest system automatically discovers and indexes methods from traits:
- Trait files are loaded before classes during manifest build
- Methods from traits appear in the model's metadata
- IDE helpers correctly locate trait method definitions
- Code quality rules validate trait methods same as class methods
Benefits:
- Keep models focused and maintainable
- Group related functionality by problem domain
- Easier to navigate large models with hundreds of methods
- Clear separation between different aspects of model behavior
- Traits are discovered and validated automatically by manifest
SITE-BASED MULTI-TENANCY
RSpade provides automatic multi-tenant data isolation through model inheritance.
Model Types:
Rsx_Model_Abstract
- Base model for global/system-wide data
- No automatic site scoping
- Examples: countries, regions, system settings
Rsx_Site_Model_Abstract
- Base model for tenant-specific data
- Automatic site_id scoping on all operations
- Examples: users, clients, projects, tasks
How It Works:
Models extending Rsx_Site_Model_Abstract automatically:
- Filter all queries by current session site_id
- Set site_id on all new records from session
- Prevent changing site_id on existing records
- Prevent cross-site data access (throws fatal error)
- Require site_id column in database table
Database Schema:
CREATE TABLE projects (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
site_id BIGINT NOT NULL, -- Added by migration normalization
name VARCHAR(255) NOT NULL,
description LONGTEXT,
INDEX idx_site_id (site_id) -- Added automatically
)
The site_id column is added automatically during migration normalization
if the model extends Rsx_Site_Model_Abstract.
Usage:
class Project_Model extends Rsx_Site_Model_Abstract
{
protected $table = 'projects';
}
// In controller with session site_id = 1
$projects = Project_Model::all();
// SELECT * FROM projects WHERE site_id = 1
$project = new Project_Model();
$project->name = 'New Project';
$project->save();
// INSERT INTO projects (site_id, name) VALUES (1, 'New Project')
Security Enforcement:
// Attempt to change site_id - FATAL ERROR
$project = Project_Model::find(1); // site_id = 1
$project->site_id = 2;
$project->save();
// FATAL: Attempted to change site_id from 1 to 2. Changing site_id is not allowed.
// Attempt to save wrong site record - FATAL ERROR
// (If somehow loaded a record from site 2 while session is site 1)
$project->save();
// FATAL: Cross-site saves are not allowed.
Admin Operations (Bypass Site Scoping):
// Access all sites (admin operations only)
$all_projects = Project_Model::without_site_scope(function () {
return Project_Model::all();
});
// Query specific site
$site_2_projects = Project_Model::without_site_scope(function () {
return Project_Model::where('site_id', 2)->get();
});
Single-Site Applications:
Even single-site applications use site_id = 1 for all records.
The architecture is identical - just happens to use only one site.
Easy upgrade path if multi-tenancy needed later.
How site_id is Determined:
site_id comes from the session (Session::get_site_id()):
- User logs in → login_user_id set in session
- User has site memberships → site_id set in session
- All queries for site models filtered by this site_id
- Changing session site_id changes which data is accessible
See: php artisan rsx:man auth (for complete authentication/site selection flow)
NAMING CONVENTIONS
Tables:
- Plural snake_case (users, user_logins, product_categories)
- Always include 'id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY'
Columns:
- snake_case for all column names
- Foreign keys must suffix with '_id' (user_id, site_id)
- Booleans must prefix with 'is_' (is_active, is_published)
- Timestamps must suffix with '_at' (created_at, updated_at, deleted_at)
- Use BIGINT for all integers, TINYINT(1) for booleans only
- All text columns use UTF-8 (utf8mb4_unicode_ci collation)
TODO
- Real-time notifications: Broadcast model changes to connected clients
- Revision tracking: Auto-increment revision column via database trigger on update
SEE ALSO
php artisan rsx:man model_fetch - Ajax ORM fetch system
php artisan rsx:man model_normalization - Database normalization process
php artisan rsx:man enums - Complete enum system documentation
php artisan rsx:man migrations - Migration system and safety