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.