# Model Package Specification ## Overview The Model package provides a robust data modeling system that matches Laravel's Eloquent functionality. It supports active record pattern, relationships, attribute casting, serialization, and model events while leveraging Dart's type system. > **Related Documentation** > - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status > - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns > - See [Testing Guide](testing_guide.md) for testing approaches > - See [Getting Started Guide](getting_started.md) for development setup > - See [Events Package Specification](events_package_specification.md) for model events ## Core Features ### 1. Base Model ```dart /// Core model implementation abstract class Model { /// Model attributes final Map _attributes = {}; /// Original attributes final Map _original = {}; /// Changed attributes final Set _changes = {}; /// Model constructor Model([Map? attributes]) { fill(attributes ?? {}); } /// Gets table name String get table; /// Gets primary key String get primaryKey => 'id'; /// Gets fillable attributes List get fillable => []; /// Gets guarded attributes List get guarded => ['id']; /// Gets attribute value dynamic operator [](String key) => getAttribute(key); /// Sets attribute value operator []=(String key, dynamic value) => setAttribute(key, value); /// Gets an attribute dynamic getAttribute(String key) { return _attributes[key]; } /// Sets an attribute void setAttribute(String key, dynamic value) { if (!_original.containsKey(key)) { _original[key] = _attributes[key]; } _attributes[key] = value; _changes.add(key); } /// Fills attributes void fill(Map attributes) { for (var key in attributes.keys) { if (_isFillable(key)) { this[key] = attributes[key]; } } } /// Checks if attribute is fillable bool _isFillable(String key) { if (guarded.contains(key)) return false; if (fillable.isEmpty) return true; return fillable.contains(key); } /// Gets changed attributes Map getDirty() { var dirty = {}; for (var key in _changes) { dirty[key] = _attributes[key]; } return dirty; } /// Checks if model is dirty bool get isDirty => _changes.isNotEmpty; /// Gets original attributes Map getOriginal() => Map.from(_original); /// Resets changes void syncOriginal() { _original.clear(); _original.addAll(_attributes); _changes.clear(); } /// Converts to map Map toMap() => Map.from(_attributes); /// Converts to JSON String toJson() => jsonEncode(toMap()); } ``` ### 2. Model Relationships ```dart /// Has one relationship class HasOne extends Relationship { /// Foreign key final String foreignKey; /// Local key final String localKey; HasOne(Query query, Model parent, this.foreignKey, this.localKey) : super(query, parent); @override Future get() async { return await query .where(foreignKey, parent[localKey]) .first(); } } /// Has many relationship class HasMany extends Relationship { /// Foreign key final String foreignKey; /// Local key final String localKey; HasMany(Query query, Model parent, this.foreignKey, this.localKey) : super(query, parent); @override Future> get() async { return await query .where(foreignKey, parent[localKey]) .get(); } } /// Belongs to relationship class BelongsTo extends Relationship { /// Foreign key final String foreignKey; /// Owner key final String ownerKey; BelongsTo(Query query, Model child, this.foreignKey, this.ownerKey) : super(query, child); @override Future get() async { return await query .where(ownerKey, parent[foreignKey]) .first(); } } ``` ### 3. Model Events ```dart /// Model events mixin mixin ModelEvents { /// Event dispatcher static EventDispatcherContract? _dispatcher; /// Sets event dispatcher static void setEventDispatcher(EventDispatcherContract dispatcher) { _dispatcher = dispatcher; } /// Fires a model event Future fireModelEvent(String event) async { if (_dispatcher == null) return true; var result = await _dispatcher!.dispatch('model.$event', this); return result != false; } /// Fires creating event Future fireCreatingEvent() => fireModelEvent('creating'); /// Fires created event Future fireCreatedEvent() => fireModelEvent('created'); /// Fires updating event Future fireUpdatingEvent() => fireModelEvent('updating'); /// Fires updated event Future fireUpdatedEvent() => fireModelEvent('updated'); /// Fires deleting event Future fireDeletingEvent() => fireModelEvent('deleting'); /// Fires deleted event Future fireDeletedEvent() => fireModelEvent('deleted'); } ``` ### 4. Model Query Builder ```dart /// Model query builder class Query { /// Database connection final DatabaseConnection _connection; /// Model instance final T _model; /// Query constraints final List _wheres = []; final List _bindings = []; final List _orders = []; int? _limit; int? _offset; Query(this._connection, this._model); /// Adds where clause Query where(String column, [dynamic value]) { _wheres.add('$column = ?'); _bindings.add(value); return this; } /// Adds order by clause Query orderBy(String column, [String direction = 'asc']) { _orders.add('$column $direction'); return this; } /// Sets limit Query limit(int limit) { _limit = limit; return this; } /// Sets offset Query offset(int offset) { _offset = offset; return this; } /// Gets first result Future first() async { var results = await get(); return results.isEmpty ? null : results.first; } /// Gets results Future> get() async { var sql = _toSql(); var rows = await _connection.select(sql, _bindings); return rows.map((row) => _hydrate(row)).toList(); } /// Builds SQL query String _toSql() { var sql = 'select * from ${_model.table}'; if (_wheres.isNotEmpty) { sql += ' where ${_wheres.join(' and ')}'; } if (_orders.isNotEmpty) { sql += ' order by ${_orders.join(', ')}'; } if (_limit != null) { sql += ' limit $_limit'; } if (_offset != null) { sql += ' offset $_offset'; } return sql; } /// Hydrates model from row T _hydrate(Map row) { var instance = _model.newInstance() as T; instance.fill(row); instance.syncOriginal(); return instance; } } ``` ## Integration Examples ### 1. Basic Model Usage ```dart // Define model class User extends Model { @override String get table => 'users'; @override List get fillable => ['name', 'email']; String get name => this['name']; set name(String value) => this['name'] = value; String get email => this['email']; set email(String value) => this['email'] = value; } // Create user var user = User() ..name = 'John Doe' ..email = 'john@example.com'; await user.save(); // Find user var found = await User.find(1); print(found.name); // John Doe ``` ### 2. Relationships ```dart class User extends Model { // Has many posts Future> posts() { return hasMany('user_id').get(); } // Has one profile Future profile() { return hasOne('user_id').get(); } } class Post extends Model { // Belongs to user Future user() { return belongsTo('user_id').get(); } } // Use relationships var user = await User.find(1); var posts = await user.posts(); var profile = await user.profile(); ``` ### 3. Events ```dart // Register event listener Model.getEventDispatcher().listen>((event) { var user = event.model; print('User ${user.name} was created'); }); // Create user (triggers event) var user = User() ..name = 'Jane Doe' ..email = 'jane@example.com'; await user.save(); ``` ## Testing ```dart void main() { group('Model', () { test('handles attributes', () { var user = User() ..name = 'John' ..email = 'john@example.com'; expect(user.name, equals('John')); expect(user.isDirty, isTrue); expect(user.getDirty(), containsPair('name', 'John')); }); test('tracks changes', () { var user = User() ..fill({ 'name': 'John', 'email': 'john@example.com' }); user.syncOriginal(); user.name = 'Jane'; expect(user.isDirty, isTrue); expect(user.getOriginal()['name'], equals('John')); expect(user.name, equals('Jane')); }); }); group('Relationships', () { test('loads relationships', () async { var user = await User.find(1); var posts = await user.posts(); expect(posts, hasLength(greaterThan(0))); expect(posts.first, isA()); }); }); } ``` ## Next Steps 1. Implement core model features 2. Add relationship types 3. Add model events 4. Add query builder 5. Write tests 6. Add benchmarks ## Development Guidelines ### 1. Getting Started Before implementing model features: 1. Review [Getting Started Guide](getting_started.md) 2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) 3. Follow [Testing Guide](testing_guide.md) 4. Use [Foundation Integration Guide](foundation_integration_guide.md) 5. Review [Events Package Specification](events_package_specification.md) ### 2. Implementation Process For each model feature: 1. Write tests following [Testing Guide](testing_guide.md) 2. Implement following Laravel patterns 3. Document following [Getting Started Guide](getting_started.md#documentation) 4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md) ### 3. Quality Requirements All implementations must: 1. Pass all tests (see [Testing Guide](testing_guide.md)) 2. Meet Laravel compatibility requirements 3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md)) 4. Support model events (see [Events Package Specification](events_package_specification.md)) ### 4. Integration Considerations When implementing model features: 1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md) 2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) 3. Use testing approaches from [Testing Guide](testing_guide.md) 4. Follow development setup in [Getting Started Guide](getting_started.md) ### 5. Performance Guidelines Model system must: 1. Handle large datasets efficiently 2. Optimize relationship loading 3. Support eager loading 4. Cache query results 5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks) ### 6. Testing Requirements Model tests must: 1. Cover all model operations 2. Test relationships 3. Verify events 4. Check query building 5. Follow patterns in [Testing Guide](testing_guide.md) ### 7. Documentation Requirements Model documentation must: 1. Explain model patterns 2. Show relationship examples 3. Cover event handling 4. Include performance tips 5. Follow standards in [Getting Started Guide](getting_started.md#documentation)