3. The model
A task has a name right now. You want a title and a done flag. In this chapter you reshape the table by changing a struct, and you learn what a migration is.
Where the table came from
Open migrations/0.0.1_create_task_table.sql:
-- Generated by prea from the models: review it before committing.
create table if not exists "task" (
"id" uuid not null default gen_random_uuid(),
"name" text not null,
primary key ("id")
);
-- prea:down
drop table "task";This is a migration: a SQL file that changes the database. In the dev profile, whenever a model differs from the database, Preá writes a migration with the difference and applies it. The model is preas/task/model.go, and this file is its translation to SQL.
Two settings in config/application-dev.yaml turn this on:
database:
migrations:
from_models: true # a model change becomes a migration at startup...
on_start: true # ...that is applied right awayprea new --with-db writes them for you. The base application.yaml leaves both off, so only dev writes and applies migrations. You'll see why in chapter 7.
No migration in migrations/?
If you created the project without --with-db and added the database by hand, these two lines are missing, and Preá writes nothing. Add them to config/application-dev.yaml and restart prea dev. To write a migration yourself, see Migrations.
Start the table over
This migration only exists on your machine, so you can still throw it away. Stop prea dev with Ctrl+C, then undo it and delete it:
prea migrate down
rm migrations/0.0.1_create_task_table.sqlundid 0.0.1_create_task_table (5ms)prea migrate down ran the part below -- prea:down, and the table is gone.
Only on your machine
Once a migration is committed and others have applied it, never edit or delete it. Write a new one instead.
Change the model
Replace preas/task/model.go:
package task
// Task is one thing to do.
type Task struct {
ID string `json:"id" pk:"" generated:"uuid"`
Title string `json:"title" size:"200"`
Done bool `json:"done" default:"false"`
}
func (Task) Table() string {
return "task"
}Each tag describes the column:
pk:""makesidthe primary key, andgenerated:"uuid"lets the database generate it.size:"200"limits the title to 200 characters.default:"false"makes new tasks start as not done.titleisnot nullbecause astringcan't be nil. A*stringwould allow null.
Change the request bodies
The files in dtos/ describe what clients send. Replace preas/task/dtos/create_request.go:
package dtos
// CreateRequest is the body of POST /task.
type CreateRequest struct {
Title string `json:"title" binding:"required"`
}And preas/task/dtos/update_request.go:
// UpdateRequest is the body of PATCH /task/:id. Only the fields you send
// change: nil keeps the column as it is.
type UpdateRequest struct {
Title *string `json:"title"`
Done *bool `json:"done"`
}The fields of the update are pointers on purpose. You'll see why in the next chapter.
Fix the service
The service still talks about Name. In preas/task/service.go, change the Create method:
func (s *Service) Create(ctx context.Context, req dtos.CreateRequest) (Task, error) {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return Task{}, core.NewError(http.StatusBadRequest, "name can't be blank")
req.Title = strings.TrimSpace(req.Title)
if req.Title == "" {
return Task{}, core.NewError(http.StatusBadRequest, "title can't be blank")
}
return s.Repo.Create(ctx, req)
}And make Update hand the request straight to the repository:
func (s *Service) Update(ctx context.Context, id string, req dtos.UpdateRequest) (Task, error) {
req.Name = strings.TrimSpace(req.Name)
return s.Repo.Update(ctx, id, req)
}Run it
prea devlevel=SUCCESS msg="database: generated a migration from the models: review it before committing" file=migrations/0.0.1_create_task_table.sql
level=INFO msg="database: applied 0.0.1_create_task_table (5ms)"A fresh 0.0.1_create_task_table.sql, now with your columns:
create table if not exists "task" (
"id" uuid not null default gen_random_uuid(),
"title" varchar(200) not null,
"done" bool not null default false,
primary key ("id")
);curl -X POST localhost:8000/task -H 'Content-Type: application/json' -d '{"title": "Buy coffee"}'{
"code": 1,
"data": {
"id": "4caff630-444a-4a86-bb31-3b5e963cfb32",
"title": "Buy coffee",
"done": false
}
}done came from the database default.
What you learned
- The model describes the table, and its tags describe the columns.
- In
dev, Preá turns model changes into migrations and applies them. - Review every migration; a local one can still be undone and regenerated.