Models
Models can sometimes be a complex aspect of any application. In this section, you'll find a rundown on how you can compose your models or database entities.
Model Structure
The first thing is to create a struct that matches your database schema. Almost all models should embed the ModelBase
struct that provides the ID
, CreatedAt
and UpdatedAt
properties. Exceptions may include a many-to-many table where you only need to store 2 identifiers. To learn more about model declarations, you can refer to Gorm's official comprehensive documentation.
Here's a Cat model that should correspond to a Cats table that contains 5 properties i.e. ID
, CreatedAt
, UpdatedAt
, Name
and Type
in a database.
type Cat struct {
ModelBase
Name string `gorm:"size:255"`
Type string `gorm:"size:255"`
}
Notice how every property contains a gorm
decoration to specify things like field size, uniqueness or foreign keys etc. For more details, please refer to Gorm's documentation.
Your model may sometimes contain properties that do not correspond to a database column. To do that, you simply need to use the gorm:"-"
decoration.
Given that this package is designed to work with multiple database servers like MySQL or Postgres, some data types may be available in some servers and not others. It's worth testing your application with different servers from time to time to accomodate easy switching of database server, unless your use case relies on a specific data type - in which case you're making a calculated decision to lock your application to that server.
Basic Common Functionality
Now that you have a structure that corresponds to a table in your database, some common functionality is in order. Generally, one would at least expect the basic CRUD functionality. Here's a basic CRUD implementation that is required for any model:
FindAll()
, for retrieving all records in the table
func (model *Cat) FindAll() (models []*Cat, err error) {
result := db.Model(model).Find(&models)
return models, result.Error
}
FindMany()
, for retrieving many items given an array of IDs
func (model *Cat) FindMany(ids []string) (models []*Cat, err error) {
result := db.Model(model).Find(&models, ids)
return models, result.Error
}
Find()
, for retrieving a single item with a given ID
func (model *Cat) Find(id string) (m *Cat, err error) {
result := db.Model(model).Where("ID=?", id).First(&m)
return m, result.Error
}
Save()
, for creating a new record in the database and assigning a new ID to it
func (model *Cat) Save() error {
return db.Model(model).Create(&model).Error
}
'Update()
, for updating a record in the database given an existing ID
func (model *Cat) Update() error {
return db.Model(model).Updates(&model).Error
}
Delete()
, for deleting a record in the database given an existing ID
func (model *Cat) Delete(id string) error {
return db.Model(model).Where("ID=?", id).Delete(&model).Error
}
All of the above functions will return an error if they cannot perform what they're supposed to. That's useful to inform users if the data they're looking for exists or is stored. For detailed utilisation of these functions, check out the handlers folder.
These functions are not abstracted to allow granular control over each model, as each individual model can quickly morph into something very large with child elements, preload functions and pagination.
Model Accessibility
Given the basic functionality defined in the previous section, we've created the ability to do something like the following:
...
catModel := &Cat{}
myCat, err := catModel.Find(catID)
if err != nil {
fmt.Println("couldn't find cat with ID", catID)
}
...
The problem with the code above is that you will need to instantiate a new struct catModel
from &Cat{}
in order to have a pointer receiver that can call the Find()
function. You can avoid that by using the following common getter structure for all models, right at the top of the model before its declaration to maintain consistency.
var cat *Cat = &Cat{}
func CatModel() *Cat {
return cat
}
The above will now create a singleton pattern that you can access from any component within the package like models.CatModel().Find()
.
the CatModel()
method should only be used to fetch data from the database. Saving, updating and deleting data should be applied to an actual instance that has been returned through a Find()
, FindAll()
or FindMany()
function.
Working with JSON Forms
Once you have retrieved the records needed from the database, you may want to send those records as a response. To do that, you can use forms. Every model is expected to have at least one method named MapToForm()
that returns a JSON representation of that model.
Forms are basic structures that may or may not exactly match all the properties that a model has. The reason it has been done this way is to enable multiple forms where one can contain all model properties e.g. intended for an admin user to view, while another may contain a sanitised version of that model e.g. intended only for a read-only user.
For more details on creating a form, scroll down to the forms section below. Here you'll find a sample implementation of MapToForm()
function.
func (model *Cat) MapToForm() *CatForm {
form := &CatForm{
Name: model.Name,
Type: model.Type,
}
form.ID = model.ID
form.CreatedAt = model.CreatedAt
form.UpdatedAt = model.UpdatedAt
return form
}
Complete Code
Here's a complete code as a model sample that you can copy as a base model.
var cat *Cat = &Cat{}
func CatModel() *Cat {
return cat
}
type Cat struct {
ModelBase
Name string `gorm:"size:255"`
Type string `gorm:"size:255"`
}
func (model *Cat) MapToForm() *CatForm {
form := &CatForm{
Name: model.Name,
Type: model.Type,
}
form.ID = model.ID
form.CreatedAt = model.CreatedAt
form.UpdatedAt = model.UpdatedAt
return form
}
func (model *Cat) FindAll() (models []*Cat, err error) {
result := db.Model(model).Find(&models)
return models, result.Error
}
func (model *Cat) FindMany(ids []string) (models []*Cat, err error) {
result := db.Model(model).Find(&models, ids)
return models, result.Error
}
func (model *Cat) Find(id string) (m *Cat, err error) {
result := db.Model(model).Where("ID=?", id).First(&m)
return m, result.Error
}
func (model *Cat) Save() error {
return db.Model(model).Create(&model).Error
}
func (model *Cat) Update() error {
return db.Model(model).Updates(&model).Error
}
func (model *Cat) Delete(id string) error {
return db.Model(model).Where("ID=?", id).Delete(&model).Error
}
Copy the code above and replace the name Cat
to get started.