Skip to content

Convention Over Configuration ​

Bavard follows the Convention over Configuration paradigm, popularized by Ruby on Rails. The idea is simple: by following sensible defaults and naming conventions, you can eliminate boilerplate configuration and focus on what makes your application unique.

Naming Conventions ​

Table Names ​

By default, Bavard expects table names to be plural and snake_case:

Model ClassExpected Table
Userusers
BlogPostblog_posts
Categorycategories
UserRoleuser_roles
dart
class User extends Model {
  @override
  String get table => 'users';  // Convention: plural, snake_case
}

Primary Keys ​

The default primary key is assumed to be id:

dart
class User extends Model {
  @override
  String get primaryKey => 'id';  // This is the default, no need to specify
}

If your table uses a different primary key, override it:

dart
class User extends Model {
  @override
  String get primaryKey => 'uuid';  // Custom primary key
}

Foreign Keys ​

Foreign keys follow the pattern {singular_table_name}_id:

RelationshipExpected Foreign Key
Post belongs to Useruser_id on posts table
Comment belongs to Postpost_id on comments table
Profile belongs to Useruser_id on profiles table
dart
// Bavard infers foreign key as 'user_id'
class Post extends Model {
  BelongsTo<User> author() => belongsTo(User.new);
  // Equivalent to: belongsTo(User.new, foreignKey: 'user_id', ownerKey: 'id')
}

Pivot Tables (Many-to-Many) ​

For many-to-many relationships, pivot tables should be named by joining the two table names in alphabetical order, separated by an underscore:

ModelsExpected Pivot Table
User ↔ Rolerole_user
Post ↔ Tagpost_tag
Category ↔ Productcategory_product

Pivot table columns follow the foreign key convention:

role_user
├── user_id
└── role_id

Polymorphic Relationships ​

Polymorphic relationships use a {name}_type and {name}_id column pair:

Morphable NameType ColumnID Column
commentablecommentable_typecommentable_id
taggabletaggable_typetaggable_id

The type column stores the table name of the parent model.

Timestamps ​

When using HasTimestamps, Bavard expects these columns:

ColumnPurpose
created_atSet when record is first created
updated_atUpdated on every save

Soft Deletes ​

When using HasSoftDeletes, Bavard expects:

ColumnPurpose
deleted_atTimestamp when record was soft-deleted (null if active)

Overriding Conventions ​

Every convention can be overridden when your schema doesn't match:

dart
class Post extends Model {
  @override
  String get table => 'blog_entries';  // Non-standard table name

  @override
  String get primaryKey => 'entry_id';  // Non-standard primary key

  // Non-standard foreign key
  BelongsTo<User> author() => belongsTo(
    User.new,
    foreignKey: 'author_uuid',
    ownerKey: 'uuid',
  );
}

Released under the MIT License. Crafted with ❤️ for the Dart ecosystem.