GORM model definition

GORM model definition

When using ORM tools, we usually need to define Models in the code to map with data tables in the database. In GORM, Models are usually normally defined structures, basic go types or their pointers. It also supports sql.Scanner and driver. Value interfaces.

GORM prefers convention to configuration. By default, GORM uses the ID as the primary key, the serpentine complex of the structure name as the table name, and the serpentine of the field name as the column name, and uses the CreatedAt and UpdatedAt fields to track the creation and update time

Following the existing conventions of GORM can reduce your configuration and code. If the agreement does not meet your needs, GORM allows you to customize and configure them

1, gorm.Model

To facilitate model definition, GORM has a GORM. Model structure built in. gorm.Model is a gorang structure containing four fields: ID, createdat, updatedat and deletedat.

// gorm.Model definition
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

You can embed it in your own model:

// Inject 'ID', 'createdat', 'updatedat' and 'deletedat' fields into the 'User' model
type User struct {
  gorm.Model
  Name string
}

// Equivalent to
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

Of course, you can completely define your own model:

// Don't use gorm.Model, define the model yourself
type User struct {
  ID   int
  Name string
}

Create / update Time tracking (nanosecond, millisecond, second, Time)

GORM contract uses CreatedAt and UpdatedAt to track creation / update time. If you define such a field, GORM will automatically fill it in when it is created and updated current time

To use fields with different names, you can configure the autoCreateTime, autoUpdateTime tags

If you want to save UNIX (milli / nanosecond) timestamps instead of time, you simply change time.Time to int

type User struct {
	CreatedAt time.Time // Set to current time if it is zero on creating
	UpdatedAt int       // Set to current unix seconds on updating or if it is zero on creating
	Updated   int64     `gorm:"autoUpdateTime:nano"`  // Use unix nano seconds as updating time
	Updated   int64     `gorm:"autoUpdateTime:milli"` // Use unix milli seconds as updating time
	Created   int64     `gorm:"autoCreateTime"`       // Use unix seconds as creating time
}

For normal structure fields, you can also embed them through the label embedded, for example:

type Author struct {
    Name  string
    Email string
}

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded"`
  Upvotes int32
}
// Equivalent to
type Blog struct {
  ID    int64
  Name  string
  Email string
  Upvotes  int32
}

In addition, you can use the label embeddedPrefix to prefix the field names in db, for example:

type Blog struct {
	ID      int
	Author  Author `gorm:"embedded;embeddedPrefix:author_"`
	Upvotes int32
}

// Equivalent to
type Blog struct {
	ID          int64
	AuthorName  string
	AuthorEmail string
	Upvotes     int32
}

2, Field level permission control

Exportable fields have full permissions when CRUD using GORM. In addition, GORM allows you to control field level permissions with labels. In this way, you can make the permission of a field read-only, write only, create only, update only, or be ignored

Note: ignored fields are not created when creating tables using GORM Migrator

type User struct {
	Name string `gorm:"<-:create"`          // Allow read and create
	Name string `gorm:"<-:update"`          // Allow read and update
	Name string `gorm:"<-"`                 // Allow read and write (create and update)
	Name string `gorm:"<-:false"`           // Read allowed, write prohibited
	Name string `gorm:"->"`                 // Read only (write is prohibited unless there is a custom configuration)
	Name string `gorm:"->;<-:create"`       // Allow read and write
	Name string `gorm:"->:false;<-:create"` // Create only (prohibit reading from db)
	Name string `gorm:"-"`                  // Reading and writing through struct will ignore this field
}

4, Structure tags

Tags are optional when you declare a model using a structure. gorm supports the following tags:

4.1 supported structure tags field labels

When declaring the model, the tag is optional. GORM supports the following tags: the tag name is case insensitive, but it is recommended to use the camelCase style

Tag name explain
column Specify the db column name
type For column data types, it is recommended to use general types with good compatibility. For example, all databases support bool, int, uint, float, string, time and bytes, and can be used with other tags, such as not null, size, autoIncrement... Specifying database data types like varbinary(8) is also supported. When using the specified database data type, it needs to be a complete database data type, such as MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
size Specify the column size, for example: size:256
primaryKey Specify column as primary key
unique Specifies that the column is unique
default Specifies the default value for the column
precision Specifies the precision of the column
scale Specify column size
not null The specified column is NOT NULL
autoIncrement Specify columns as auto grow
autoIncrementIncrement Automatic step size to control the interval between successive records
embedded Nested fields
embeddedPrefix Column name prefix for embedded fields
autoCreateTime The current time is tracked during creation. For the int field, it will track the second time stamp. You can use nano/milli to track the nanosecond and millisecond time stamps, for example: autoCreateTime:nano
autoUpdateTime The current time is tracked when creating / updating. For the int field, it will track the second time stamp. You can use nano/milli to track the nanosecond and millisecond time stamps, for example: autoUpdateTime:milli
index Create an index based on parameters. If multiple fields use the same name, create a composite index to view Indexes Get details
uniqueIndex Same as index, but a unique index is created
check Create a check constraint, for example, check: age > 13, view constraint Get details
<- Set the permission for field writing, < -: create only, < -: update only, < -: false no write permission, < - create and update permission
-> Set field read permission, ->: false no read permission
- Ignore this field, - no read / write permission
comment Add comments to fields during migration

4.2 associated labels

label describe
foreignKey Specify the columns of the current model as the foreign key of the join table
references Specifies the column name of the reference table, which will be mapped to the foreign key of the join table
polymorphic Specify polymorphic types, such as model names
polymorphicValue Specify polymorphic value, default table name
many2many Specify connection table name
joinForeignKey Specifies the foreign key column name of the join table, which will be mapped to the current table
joinReferences Specifies the foreign key column name of the join table, which will be mapped to the reference table
constraint Relationship constraints, such as OnUpdate, OnDelete

5, Model definition example

package main

import (
	"database/sql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"time"
)

type User struct {
	gorm.Model
	Name         string
	Age          sql.NullInt64
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"`        // Set the field size to 255
	MemberNumber *string `gorm:"unique;not null"` // Set the member number to be unique and not empty
	Num          int     `gorm:"AUTO_INCREMENT"`  // Set num to auto increment type
	Address      string  `gorm:"index:addr"`      // Create an index named addr for the address field
	IgnoreMe     int     `gorm:"-"`               // Ignore this field
}

func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	// Migration table creation correspondence
	db.AutoMigrate(&User{})

}

Modify the field to be given

package main

import (
	"database/sql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"time"
)

type User struct {
	gorm.Model
	Name         string `gorm:"type:varchar(100)"` // Increase / decrease field limit
	Age          sql.NullInt64
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"`        // Set the field size to 255
	MemberNumber *string `gorm:"unique;not null"` // Set the member number to be unique and not empty
	Num          int     `gorm:"AUTO_INCREMENT"`  // Set num to auto increment type
	Address      string  `gorm:"index:addr"`      // Create an index named addr for the address field
	IgnoreMe     int     `gorm:"-"`               // Ignore this field
}

func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	// Migration table creation correspondence
	db.AutoMigrate(&User{})

}

6, Conventions of primary key, table name and column name

6.1 Primary Key

GORM uses the field named ID as the primary key of the table by default.

type User struct {
  ID   string // The field named 'ID' will be the primary key of the table by default
  Name string
}

// Use 'AnimalID' as the primary key
// Animal uses' AnimalID 'as the primary key
type Animal struct {
	AnimalID int64 `gorm:"primary_key"`
	Name     string `gorm:"type:varchar(100);comment:full name"`
	Age      int64
}
func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	// Migration table creation correspondence
	db.AutoMigrate(&User{}, &Animal{})
}

6.2 Table Name

By default, the table name is the plural of the structure name, for example:

package main

import (
	"database/sql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"time"
)

type User struct {
	gorm.Model
	Name         string `gorm:"type:varchar(100);comment:full name"`
	Age          sql.NullInt64
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"`        // Set the field size to 255
	MemberNumber *string `gorm:"unique;not null"` // Set the member number to be unique and not empty
	Num          int     `gorm:"AUTO_INCREMENT"`  // Set num to auto increment type
	Address      string  `gorm:"index:addr"`      // Create an index named addr for the address field
	IgnoreMe     int     `gorm:"-"`               // Ignore this field
}

// TableName sets the User's table name to ` profiles`
func (User) TableName() string {
	return "user_randy"
}

func (u User) TableName() string {
  if u.Role == "admin" {
    return "admin_users"
  } else {
    return "users"
  }
}


func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
    // Disable the plural form of the default table name. If it is set to true, the default table name of 'user' is' user '`
	db.SingularTable(true)
    
	// Migration table creation correspondence
	db.AutoMigrate(&User{},)

}

You can also specify the table name through Table():

// Use the User structure to create a structure named ` deleted_users' table
db.Table("deleted_users").CreateTable(&User{})

var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';

GORM also supports the rule to change the default table name:

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
  return "prefix_" + defaultTableName;
}

6.3 Column Name

Column names are generated by underlining field names

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}

You can use the struct tag to specify the column name:

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}

6.3 timestamp tracking

CreatedAt

If the model has a CreatedAt field, the value of this field will be the time when the record was first created.

db.Create(&user) // `CreatedAt ` will be the current time

// You can use the 'Update' method to change the value of 'CreateAt'
db.Model(&user).Update("CreatedAt", time.Now())

UpdatedAt

If the model has an UpdatedAt field, the value of this field will be the time of each record update.

db.Save(&user) // `UpdatedAt ` will be the current time

db.Model(&user).Update("name", "jinzhu") // `UpdatedAt ` will be the current time

DeletedAt

If the model has a DeletedAt field, when you call Delete to Delete the record,

7, Field defaults

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type Users struct {
	gorm.Model
	Name string `gorm:"type:varchar(100);comment:full name"`
	Age  int64
	// Set defaults
}

func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// Migration table creation correspondence
	db.AutoMigrate(&Users{})
	db.Debug().Create(&Users{Name: "randy", Age: 18})
	db.Debug().Create(&Users{Age: 18}) // Add record name is Null by default

}

Set default values for model

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type Users struct {
	gorm.Model
	Name string `gorm:"type:varchar(100);default:RandySun;comment:full name"`
	Age  int64
	// Set defaults
}

func main() {
	dsn := "root:@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// Migration table creation correspondence
	db.AutoMigrate(&Users{})
	db.Debug().Create(&Users{Age: 19}) // Add record name is Null by default
}

Note: by defining the default value of the field through the tag, the SQL statement generated when creating the record will exclude the field with no value or zero value. After inserting records into the database, Gorm loads the default values of those fields from the database.

for instance:

var Users = User{Name: "", Age: 99}
db.Create(&user)

The SQL statement actually executed by the above code is INSERT INTO users("age") values('99 ');, The zero value field Name is excluded, and in the database, this data will use the set default value RandySun as the value of the Name field.

Note that the zero values of all fields, such as 0, "",false or other zero values, will not be saved to the database, but their default values will be used. If you want to avoid this situation, you can consider using pointers or implementing the Scanner/Valuer interface, such as:

Using pointer to store zero value in database

// Use pointer
type Users struct {
	gorm.Model
	// Set defaults
	Name *string `gorm:"type:varchar(100);default:RandySun;comment:full name"` 
	Age  int64
}
db.Debug().Create(&Users{Name: new(string), Age: 19}) // Add record name is Null by default

Use the Scanner/Valuer interface to store the zero value into the database

// Using Scanner/Valuer
type Users struct {
	gorm.Model
	//Name *string `gorm:"type:varchar(100);default:RandySun;comment: name"`
	Name sql.NullString `gorm:"type:varchar(100);default:RandySun;comment:full name"`
	Age  int64
	// Set defaults
}

db.Debug().Create(&Users{Name: sql.NullString{String: "", Valid: true}, Age: 19}) // Add record name is Null by default

Extension creation options

For example, in the PostgreSQL database, you can use the following methods to implement merge insertion, update if there is one, and insert if there is none.

// Add extended SQL options for Instert statements
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;

Posted on Tue, 30 Nov 2021 14:27:59 -0500 by emopoops