Creating Models
Each database table corresponds to a class that extends Model.
Basic Model
At a minimum, you must override table and fromMap.
import 'package:bavard/bavard.dart';
import 'package:bavard/schema.dart';
class User extends Model {
@override
String get table => 'users';
// Define schema for query safety and automatic casting
@override
List<SchemaColumn> get columns => [
IdColumn(),
TextColumn('name'),
BoolColumn('is_active'),
];
// Constructor that passes attributes to the super class
User([super.attributes]);
// Factory method for hydration
@override
User fromMap(Map<String, dynamic> map) => User(map);
}Relationships & getRelation
Mandatory for Relationships
If your model defines relationships (e.g., hasMany, belongsTo), you must override the getRelation method. This method is critical for eager loading and relationship resolution. Without it, Bavard will not be able to find and load related models dynamically.
This method maps relationship names to their corresponding definition methods:
class User extends Model {
HasMany<Post> posts() => hasMany(Post.new);
@override
Relation? getRelation(String name) {
if (name == 'posts') return posts();
return super.getRelation(name);
}
}Accessing Attributes
By default, attributes are stored in a Map<String, dynamic>.
final user = User();
// Setter
user.attributes['name'] = 'Mario';
// Getter
print(user.attributes['name']);Typed Helpers
Bavard includes the HasAttributeHelpers mixin by default, which provides cleaner access:
// Bracket notation
user['name'] = 'Mario';
// Typed getters
String? name = user.string('name');
int? age = user.integer('age');
bool? active = user.boolean('is_active');Manual Implementation (No Code Generation)
While code generation is recommended to reduce boilerplate, you can define your models using standard Dart code. This gives you full control and requires no background processes.
To implement a model manually, you should:
- Define explicit getters and setters using
getAttribute<T>()andsetAttribute(). - Override the
columnslist to define the schema and automatic casting. - (Optional) Define
fillableorguardedattributes for mass assignment.
class User extends Model {
@override
String get table => 'users';
User([super.attributes]);
@override
User fromMap(Map<String, dynamic> map) => User(map);
// 1. Explicit Getters & Setters
String? get name => getAttribute<String>('name');
set name(String? value) => setAttribute('name', value);
int? get age => getAttribute<int>('age');
set age(int? value) => setAttribute('age', value);
// 2. Define Schema (Enables automatic casting)
@override
List<SchemaColumn> get columns => [
IntColumn('age'),
BoolColumn('is_active'),
JsonColumn('metadata'),
];
// 3. Mass Assignment Protection
@override
List<String> get fillable => ['name', 'age'];
}Model with Code Generation (Recommended)
For full type safety and better IDE support, use the @fillable annotation and build_runner.
- Annotate the class and add the mixin.
- Define the schema in
static const schemaTypes. - Add the part directive.
import 'package:bavard/bavard.dart';
part 'user.fillable.g.dart'; // Name of the generated file
@fillable
class User extends Model with $UserFillable {
@override
String get table => 'users';
static const schema = (
id: IdColumn(),
createdAt: CreatedAtColumn(),
name: TextColumn('name'),
email: TextColumn('email'),
age: IntColumn('age'),
isActive: BoolColumn('is_active'),
);
User([super.attributes]);
@override
User fromMap(Map<String, dynamic> map) => User(map);
}Run the generator:
dart run build_runner buildNow you can use typed accessors:
user.name = 'Mario';
user.age = 30;
print(user.email);Dirty Checking
Bavard tracks changes made to a model's attributes. This allows it to perform optimized UPDATE queries that only modify the columns that have actually changed.
isDirty([attribute]): Returnstrueif the model or a specific attribute has been modified.getDirty(): Returns aMapof all modified attributes and their new values.
final user = await User().query().find(1);
user.name = 'Updated Name';
print(user.isDirty()); // true
print(user.isDirty('name')); // true
print(user.isDirty('email')); // false
print(user.getDirty()); // {'name': 'Updated Name'}
await user.save(); // Only 'name' will be updated in the DB
print(user.isDirty()); // false