Skip to content

Define a schema

You’ll write the schema the other guides use: authors, posts and comments with relations both ways, an enum, an array column, an index and a check constraint.

lib/models/blog.gisila.yaml:

Author:
columns:
name:
type: varchar
is_null: false
email:
type: varchar
is_null: false
is_unique: true
allow_blank: false
created_at:
type: timestamp
is_null: false
default: now
  • Columns are nullable unless you say is_null: false.
  • There’s no id: one is added as BIGSERIAL PRIMARY KEY.
  • default: now becomes DEFAULT NOW(), so inserts can leave created_at out.
  • allow_blank: false adds an empty-string check to the generated validate().

Flow style (name: { type: varchar, is_null: false }) is equivalent; the rest of this guide uses it to save space.

enums:
PostStatus: [draft, published, archived]
Post:
columns:
title: { type: varchar, is_null: false, max_length: 200 }
body: { type: text }
status: { type: PostStatus, is_null: false, default: draft }
published_at: { type: timestamp }
tags: { type: varchar[], is_null: false, default: '{}' }
author:
type: Author
is_null: false
on_delete: CASCADE
reverse_name: posts
  • author: { type: Author } is a foreign key. It’s stored as author_id BIGINT NOT NULL referencing authors(id), and the Dart field is authorId.
  • on_delete: CASCADE deletes an author’s posts with the author. The default is SET NULL, which requires the column to be nullable.
  • reverse_name: posts generates Author.posts. Without it, you can only navigate from post to author.
Comment:
columns:
body: { type: text, is_null: false }
post:
type: Post
is_null: false
on_delete: CASCADE
reverse_name: comments
author:
type: Author
reverse_name: comments

Add to Post:

Post:
columns:
# ...as above...
indexes:
idx_posts_author_published:
columns: [author, published_at]
checks:
published_has_date:
expression: "status <> 'published' OR published_at IS NOT NULL"

Index columns use the YAML names; author becomes author_id in the SQL.

Terminal window
dart run gisila_orm:generate

If the file has mistakes, nothing is written and you get every problem at once:

error[unknown_reference]: "Comment.post" references unknown model "Psot"
--> lib/models/blog.gisila.yaml:31:13
|
31 | type: Psot
| ^^^^ did you mean "Post"?
import 'package:blog/models/blog.gisila.g.dart';
// Models
Post(title: 'Hi', status: PostStatus.draft, tags: const [], authorId: 1);
// Typed columns
PostTable.status; // ColumnRef<PostStatus>
PostTable.tags; // ColumnRef<List<String>>
PostTable.authorId; // ColumnRef<int>
// Relations
Post.author; // BelongsToRelation<Post, Author>
Author.posts; // HasManyRelation<Author, Post>
Post.comments; // HasManyRelation<Post, Comment>
Comment.author; // BelongsToRelation<Comment, Author>
Author.comments; // HasManyRelation<Author, Comment>
// Query accessors
authors(); posts(); comments();

As the schema grows, split it: lib/models/author.gisila.yaml, lib/models/post.gisila.yaml and so on. Models can reference models in other files. With more than one file the output becomes lib/models/schema.gisila.g.dart, so update your imports.

Terminal window
dart analyze
dart run gisila_orm:migrate up

migrate up applies the generated migration; check the tables:

Terminal window
docker compose exec db psql -U postgres -d blog -c '\d posts'

You should see author_id bigint not null, the status column of type post_status, the foreign key and the published_has_date check.