Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~20 мин +50 XP

Модели и соглашения: gorm.Model, теги, именование

GORM ничего не знает о ваших данных, пока вы не объявили структуру — модель это и есть весь контракт между Go и таблицей. Разберём соглашения, из которых растут users вместо User и full_name вместо FullName, встроенный gorm.Model с четырьмя служебными полями, теги колонок и индексов, переопределение имён через TableName и NamingStrategy — и ловушку, из-за которой опечатка в теге молча не срабатывает.

Содержание урока · 10 разделов

Открываете базу после первых экспериментов, а там таблица todo_items и колонка full_name — откуда? Это не GORM капризничает, это соглашения сработали. Модель в GORM — самая обычная структура Go: никакого базового класса, никакой кодогенерации, никаких DSL. GORM один раз читает поля через рефлексию при первом обращении к модели, кэширует распарсенную схему и дальше работает с ней без затрат. Ключевая идея библиотеки — соглашения вместо конфигурации: если структура называется User, таблица будет users; если поле FullName, колонка будет full_name. Правила простые, но их надо знать наизусть, потому что по ним живёт каждый запрос. А когда соглашение мешает — есть два уровня ручной настройки: теги на полях и стратегия именования.

Соглашения об именовании: users, full_name, todo_items#

Три правила. Первое: имя таблицы — snake_case во множественном числе от имени структуры. User превращается в users, TodoItem — в todo_items, Address — в addresses (склонение делает библиотека inflection, и она знает про нерегулярные формы вроде person → people). Второе: имя колонки — snake_case от имени поля. FullName становится full_name, CreatedAt — created_at. Третье: Go-аббревиатуры GORM распознаёт и не разрывает — UserID превратится в user_id, а не в u_s_e_r_i_d, APIKey — в api_key, HTTPStatus — в http_status. Список «понятных» аббревиатур зашит в код: ID, API, URL, HTTP, JSON, SQL, UUID и другие.

Эти же правила работают на автоматические таблицы связей many-to-many — их мы будем разбирать в уроке 13, и там знание конвенций прямо спасает от сюрпризов. Пока достаточно запомнить: всё, что Go пишет CamelCase, база видит в snake_case, а единственное число становится множественным на уровне таблицы.

Структура и то, что получит база

GO
type TodoItem struct {
        ID      uint
        Title   string
        Done    bool
        DueDate time.Time
}

// Таблица: "todo_items"
// Колонки: id, title, done, due_date

Ни один тег не написан — GORM выведет всё из имён. ID получил роль первичного ключа автоматически: поле с именем ID и типом uint распознаётся как автоинкрементный ключ без явного primaryKey. Комментарий внизу — то, что создаст AutoMigrate, о котором подробнее в уроке 04.

gorm.Model: что встраивает по умолчанию#

Почти каждая модель в реальных проектах начинается со встраивания gorm.Model — стандартного набора служебных полей. Вот его настоящее определение из исходников:

Код

GO
type Model struct {
        ID        uint           `gorm:"primaryKey"`
        CreatedAt time.Time
        UpdatedAt time.Time
        DeletedAt gorm.DeletedAt `gorm:"index"`
}

type Post struct {
        gorm.Model // встраивание — поля «врастают» в Post
        Title string
        Body  string
}

// Таблица "posts" получит колонки:
// id, created_at, updated_at, deleted_at, title, body

Четыре поля, четыре обязанности. ID — автоинкрементный первичный ключ. CreatedAt GORM заполняет текущим временем при вставке, UpdatedAt — при каждом обновлении: руками трогать их не нужно. DeletedAt — самый хитрый: тип gorm.DeletedAt включает мягкое удаление — после Delete строка не удаляется, а лишь помечается временем, и обычные SELECT-запросы перестают её видеть. Подробно разберём в уроке 09; здесь важно, что колонка снабжена индексом, потому что deleted_at попадает почти в каждый запрос.

Теги полей: колонки, индексы, ограничения#

Когда имя поля недостаточно или нужна особая логика, в игру входят структурные теги. Тег gorm — строка с настройками через точку с запятой, каждая вида ключ или ключ:значение. Теги читаются при разборе схемы — то есть один раз, при первом использовании модели. Самые ходовые: column переименовать колонку, size задать размер текстового типа, uniqueIndex повесить уникальный индекс, index — обычный, type вписать SQL-тип вручную, default указать значение по умолчанию, not null запретить пустоты, а дефис — полностью выкинуть поле из таблицы (удобно для кэша и вычисляемых вещей).

Частые теги GORM#

ТегДействиеПример
primaryKeyполе становится первичным ключомgorm:"primaryKey"
columnпереименовать колонкуgorm:"column:description"
sizeразмер строкового типаgorm:"size:64"
typeявный SQL-типgorm:"type:decimal(10,2)"
uniqueIndexуникальный индексgorm:"uniqueIndex"
indexобычный индексgorm:"index"
defaultзначение по умолчаниюgorm:"default:0"
not nullзапрет NULLgorm:"not null"
-исключить поле из таблицыgorm:"-"

Модель каталога с тегами

GO
type Product struct {
        gorm.Model
        SKU   string  `gorm:"size:64;uniqueIndex"`
        Title string  `gorm:"not null"`
        Price float64 `gorm:"type:decimal(10,2)"`
        Note  string  `gorm:"column:description;default:''"`
        Cache string  `gorm:"-"`
}

// В таблице "products" появятся:
// id, created_at, updated_at, deleted_at,
// sku (varchar(64), unique), title (not null),
// price (decimal(10,2)), description (default '')

SKU короткий и обязанный быть уникальным — size:64 плюс uniqueIndex. Price — деньги, значит точный тип decimal, а не double, который накапливает ошибку округления. Note переименована в description тегом column и получает дефолт. Cache вообще не попадёт в базу — это поле времени исполнения.

TableName и NamingStrategy: своя логика именования#

Соглашения — это дефолт, а не закон. Метод TableName возвращает имя таблицы напрямую и имеет высший приоритет: хватит, когда работаете с legacy-схемой, где таблицы называются по-другому, или когда компания держит префиксы. Если правило глобальное (например, все таблицы с префиксом shop_), вместо десятка методов TableName настраивается NamingStrategy в конфиге подключения: TablePrefix добавляет префикс ко всем таблицам, SingularTable запрещает множественное число — структура User будет мапиться на user, а не users. Есть и NameReplacer для точечных замен имён. Стратегию передают в gorm.Open через поле NamingStrategy конфига, тип берётся из пакета gorm.io/gorm/schema.

Переопределяем имена

GO
// Точечно: для одной модели
func (Product) TableName() string {
        return "catalog_products"
}

// Глобально: для всех моделей
import "gorm.io/gorm/schema"

db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        NamingStrategy: schema.NamingStrategy{
                TablePrefix:   "shop_",
                SingularTable: true,
        },
})
// Product теперь живёт в "shop_product"

Метод TableName объявляют на значении-приёмнике, не на указателе — GORM вызывает его и на значениях. После включения TablePrefix все таблицы получат префикс shop_, а SingularTable оставит имена в единственном числе. Миграции и запросы подхватят новые имена автоматически.

Типы полей: от string до time.Time#

Тип колонки GORM выводит из типа поля, а точный SQL-тип зависит от диалекта. Строка без size превращается в text (Postgres) или varchar нужного размера; int — в bigint, bool — в boolean, float64 — в double precision, time.Time — в timestamptz, []byte — в binary/blob. Правило денег повторю отдельно: для сумм используйте явный тип:decimal или целые копейки в int — float64 в базе — источник «копеечных» расхождений в бухгалтерии.

Отдельная история — отсутствующие значения. Обычный string не различает пустую строку и NULL: оба превратятся в "". Если колонка допускает NULL, поле делают указателем (string, time.Time) — GORM запишет NULL, когда указатель nil. Альтернатива — типы sql.NullString и compañía из стандартной библиотеки; сравнение обоих подходов и третья опция, sql.Null[T] из Go 1.22, ждут вас в уроке 21. А если хочется хранить в колонке структуру или карту — это тег serializer:json и пользовательские типы, урок 22.

Проверка: соглашения об именовании

Как GORM назовёт таблицу для структуры TodoItem?

Проверка: молчаливые теги

Поле помечено gorm:"uniquIndex" (опечатка). Что произойдёт при миграции?

Какими будут имя таблицы и колонка?

Прочитайте код и выберите, что он выведет:

type Order struct {
        gorm.Model
        OrderNo string `gorm:"size:32;uniqueIndex"`
        Total   float64
}

// Как назовутся таблица и колонка OrderNo?

Заполните пропуски: модель статьи

Восстановите теги и метод переопределения имени таблицы: у Slug — размер 64 и уникальный индекс, у Views — дефолт 0, таблица — blog_articles.

type Article struct { gorm.Model Slug string `gorm:":64;unique"` Title string Views int `gorm:":0"` } func (Article) () string { return "blog_articles" }

Карточки: словарь моделей

Термин

Соглашение об именовании таблиц

нажмите, чтобы перевернуть
Определение

snake_case во множественном числе от имени структуры: User → users, TodoItem → todo_items. Глобально меняется NamingStrategy, точечно — метод TableName.

1 / 6

Что дальше по курсу#

Модель описана — пора её материализовать. В четвёртом уроке — автомиграции: что делает AutoMigrate, чего он не умеет и когда нужны версионированные миграции. В пятом и шестом — создание записей и чтение через First и Find. Теги индексов мы использовали мельком — в уроке 18 разберём индексы всерьёз: составные, частичные и как проверить план запроса. А если хочется видеть, как модели живут в настоящем проекте, — финальный урок курса собирает слой данных блога целиком.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Модель — обычная структура Go: GORM разбирает её рефлексией один раз и кэширует схему.
  • Таблица — множественное число в snake_case, колонка — snake_case поля; UserID даёт user_id.
  • gorm.Model добавляет ID, CreatedAt, UpdatedAt и DeletedAt с индексом (мягкое удаление).
  • Теги уточняют схему: column, size, type, uniqueIndex, index, default, not null, дефис для исключения поля.
  • TableName() переопределяет имя таблицы точечно, NamingStrategy — глобально (префиксы, единственное число).
  • Опечатка в теге не вызывает ошибку — проверяйте фактическую схему после миграции.
  • Для денег — decimal или целые копейки; NULL моделируется указателями или sql.Null* (урок 21).

Домашнее задание

Объявите модель Invoice: gorm.Model, поле Number с size:32 и уникальным индексом, Amount с типом decimal(12,2), поле internalMemo с тегом дефис. Локально подключите SQLite, выполните AutoMigrate и откройте файл базы (sqlite3 app.db, затем .schema invoices): убедитесь, что колонки называются так, как вы ожидали, уникальный индекс существует, а internalMemo в таблице нет.

Личные заметкиПусто
Хранятся только в вашем браузере (GORM · Модели и соглашения: gorm.Model, теги, именование)

Вопросы и ответы по уроку

Обязательно ли встраивать gorm.Model?

Нет: модель может быть любой структурой без gorm.Model — тогда не будет автотаймстампов и мягкого удаления, а первичным ключом станет поле с именем ID или тег primaryKey. Для таблиц-логов и join-таблиц чистые структуры — норм.

Как назвать колонку, если поле называется APIKey?

api_key: GORM знает список Go-аббревиатур (ID, API, URL, HTTP, JSON и другие) и не рвёт их на буквы. Полный список — в исходниках пакета gorm.io/gorm/schema.

Что делать, если таблица уже существует со «странными» именами?

Переопределите имена точечно: метод TableName() для таблицы и тег column для колонок. Либо настройте NamingStrategy, если закономерность одна на весь проект — например, префикс legacy_.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

sqlc25 уроков~12 чпрактика25 уроков уже доступны

sqlc: типобезопасный код из SQL-запросов

Курс sqlc: настройка, запросы, параметры, NULL-типы, транзакции, джойны, пагинация, CI и сравнение с ORM. 25 уроков с практикой.

  • Настраивать sqlc.yaml для Postgres/MySQL/SQLite
  • Писать аннотированные запросы с типизированными параметрами
  • Работать с NULL, массивами и JSON-колонками
  • Организовывать транзакции через WithTx