gorm many2many and additional fields in association table - postgresql

I have a many2many association (it is used to return JSON). It's declared in a model:
// models/school.go
type School struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null"`
Accreditations []Accreditation `gorm:"many2many:school_accreditation;"`
}
It works well. I have the association returned in the json. The problem is that I have an additional field in the school_accreditation table but it isn't included in the response.
I have tried to declare a model for the association like proposed in this answer:
// models/schoolAccreditation.go
package models
import "time"
// many to many
type SchoolAccreditation struct {
StartedAt time.Time `gorm:"not null"`
}
But it doesn't work so far. Is there some additional configuration to declare? Or to modify?

Answering to myself, I added the field in the linked model as "ignore" and it works, the column is automatically retrieved from the association table.
type Accreditation struct {
// "accreditation" table
ID int `gorm:"primary_key"`
Name string
Description string
// "school_accreditation table", so the field is set as ignore with "-"
EndAt time.Time `gorm:"-"`
}

Related

GORM foreign key doesn't seem to add proper fields

I have the following model:
type Drink struct {
gorm.Model // Adds some metadata fields to the table
ID uuid.UUID `gorm:"type:uuid;primary key"`
Name string `gorm:"index;not null;"`
Volume float64 `gorm:"not null;type:decimal(10,2)"`
ABV float64 `gorm:"not null;type:decimal(10,2);"`
Price float64 `gorm:"not null;type:decimal(10,2);"`
Location Location `gorm:"ForeignKey:DrinkID;"`
}
type Location struct {
gorm.Model // Adds some metadata fields to the table
ID uuid.UUID `gorm:"primary key;type:uuid"`
DrinkID uuid.UUID
Name string `gorm:"not null;"`
Address string `gorm:"not null;type:decimal(10,2)"`
Phone int `gorm:"not null;type:decimal(10,0);"`
}
however, when I run the program, it adds both tables, however there is no location field in the Drink table.
My database looks like this after the migrations, regardless of whether I drop the tables previously:
I have a sneaking feeling it might be because I am not using the gorm default ID, but if that's the case can anyone point me to how to override the default ID with a UUID instead of a uint the proper way? or if that's not even the issue, please, I've been working on this for a few days now and I really don't want to take the "easy" road of just using the defaults gorm provides, I actually want to understand what is going on here and how to properly do what I am trying to do. I am getting no errors when running the API, and the migration appears to run as well, it's just the fields I have defined are not actually showing up in the database, which means that the frontend won't be able to add data properly.
What I WANT to happen here is that a list of stores will be available in the front-end, and when a user adds a drink, they will have to select from that list of stores. Each drink added should only have 1 store, as the drinks prices at different stores would be different. So technically there would be many "repeated" drinks in the drink table, but connected to different Locations.
First point is as you are using custom primary key, you should not use gorm.Model as it contains ID field in it. Reference
Second point is according to your description, store (location) has one to
many relationship with drink. That means a store can have multiple
drinks but a drink should belong to only one store. In one-to-many
relationship there should be a reference or relation id in the many
side. That means in your case in drink table. Then your struct
should look like this:
MyModel Struct
type MyModel struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
Location Struct (Store)
type Location struct {
MyModel
ID uuid.UUID `gorm:"primary key;type:uuid"`
// other columns...
Drinks []Drink
}
Drink Struct
type Drink struct {
MyModel
ID uuid.UUID `gorm:"type:uuid;primary key"`
//other columns...
LocationID uuid.UUID// This is important
}
Then gorm will automatically consider LocationID in drink table will be referring the ID field of Location Table. You can also explicitly instruct this to gorm using gorm:"foreignKey:LocationID;references:ID" in Location struct's Drinks array field.
Reference

AutoMigrate does not generate self-referencing foreign key

I am trying to get self-referential keys to work in my models. For some reason, the foreign key relationship just doesn't get created. I have tried a bunch of variations of the struct tags but to no avail.
I have my own Mixin:
type Mixin struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Deleted bool `json:"deleted"`
}
The issues arises in this model, where the ParentTaskID foreign key constraint doesn't get created when I run AutoMigrate on Postgres.
type Task struct {
Mixin
Content string `gorm:"not null; default:Empty Task" json:"content"`
Deadline time.Time `json:"deadline,omitempty"`
Priority uint `json:"priority,omitempty"`
Complete bool `json:"complete,omitempty"`
// Relations
BucketID *uint `gorm:"not null" json:"bucket_id"`
Bucket *Bucket `gorm:"foreignKey: BucketID" json:"-"`
ParentTaskID *uint `gorm:"not null" json:"parent_task_id"`
ParentTask *Task `gorm:"foreignKey: ParentTaskID" json:"-"`
}
I can confirm that this appears to be a bug in gorm. I have created an issue and a test case that reproduces the bug.
You can subscribe to updates in github.

Update fields create new foreignkey items instead of updating them

I've got a one-to-one relationship, Location, working with postgresql:
type App struct {
gorm.Model
PersoID string `gorm:"primary_key;unique" json:"perso_id"`
Location OllyLocation `gorm:"foreignkey:location_id"`
LocationID *uint `json:"-" gorm:"type:integer REFERENCES locations(id)"`
Users []User `json:"users,omitempty" gorm:"many2many:app_users;"`
}
type Location struct {
ID uint `json:"-" gorm:"primary_key;unique"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time
Lat float64 `json:"lat"`
Long float64 `json:"long"`
Address string `json:"address"`
Country string `json:"country"`
}
But as you can see, the App structure has its own primaryKey id as string (and the GORM one).
Thus this block from doing a simple db.Save() from gorm.
So I've tried to do:
func (previousModel *App) UpdateAndReturnedUpdated(db *gorm.DB, appUpdated *App) error {
if err := db.Model(previousModel).Update(appUpdated).Error; err != nil {
return err
}
return db.Preload("Location").First(previousModel, App{PersoID: appUpdated.PersoID}).Error
}
With this, the App is correctly updated - fantastic! - but not the Location.
The location is re created in the db with the new values and the foreignKey LocationID is updated on the App.
This is super annoying because if I want to do an update of the address only without specifying other fields, they are just re created with the original type value (lat / long will be 0)
Any ideas about how to update the foreignKey? Thanks!
You can use this to not modify the associated entities
db.Set("gorm:save_associations", false).Save(previousModel)
or to save only one field of the struct
db.Model(previousModel).UpdateColumn("PersoID", appUpdated.PersoID)
if you want to update an associated entity and not the relation
you will need to update the entity itself
db.Model(Location{ID:*previousModel.LocationID}).Update(appUpdated.Location)
Well, once more (but let's not blame the guy, he is alone) the main problem comes from an incomplete documentation.
All my problems were solved when I checked that I properly preloaded my foreign object + indicate it's ID (which blocked the fact that the foreignKey was Inserted instead of Updated)
Then, to be able to update it properly I had to do:
db.
Model(previousModel).
UpdateColumns(appUpdated).
Model(&previousModel.Location).
Updates(appUpdated.Location)
Was very painful, but at least it's solved.

How do you do UUID in Golangs Gorm?

I have the following model...
type User struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
}
I've tried several different ways, but this way throws (pq: relation "users" does not exist). I have no related models, it's literally just that one model.
I've tried using...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4())
return nil
}
Along with a uuid lib, but had no luck with that either.
Turns out I was trying to store the UUID as the wrong type, I was doing...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4())
return nil
}
When it needed to be...
func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
For postgresql, here is what I did:
go get github.com/google/uuid
Use uuid.UUID (from "github.com/google/uuid"), as type,
e.gID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()"`
Add uuid-ossp extension for postgres database,
e.g
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
Then, when you call DB's Create() method, the uuid is generated automatically.
Update: pg14+ gen_random_uuid()
(as mentioned in Doron Segal's comment)
pg 14 has built-in function gen_random_uuid() to generate uuid v4, e.g:
create table:
create table uuid_test (uid text default gen_random_uuid());
insert a row:
insert into uuid_test(uid) values (DEFAULT);
Then uid column is generated automatically.
Similiar, in go you can use the function as defaul value I think, e.g:
ID uuid.UUID gorm:"type:uuid;default:gen_random_uuid()"
BTW, the gen_random_uuid() function only support uuid v4 now, to use other versions, you still need uuid-ossp extension.
For this you will need gorm and go.uuid
go get github.com/jinzhu/gorm
go get github.com/satori/go.uuid
Try creating your own model base model in place of gorm.Model like so:
type Base struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
You would then populate this field using a method called before creation of any record, like so:
func (base *Base) BeforeCreate(scope *gorm.Scope) error {
id, err := uuid.NewV4()
if err != nil {
return err
}
return scope.SetColumn("ID", uuid.String())
}
Therefore, for your particular case, you would have:
type User struct {
Base
FirstName string `form:"first_name" json:"first_name,omitempty"`
LastName string `form:"last_name" json:"last_name,omitempty"`
Password string `form:"password" json:"password" bindind:"required"`
Email string `gorm:"type:varchar(110);unique_index" form:"email" json:"email,omitempty" binding:"required"`
Location string `form:"location" json:"location,omitempty"`
Avatar string `form:"avatar" json:"avatar,omitempty"`
BgImg string `form:"bg_img" json:"bg_img,omitempty"`
}
More details on this can be found here
This was my solution for Gorm v1.21
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/google/uuid
import (
"gorm.io/gorm"
"github.com/google/uuid"
)
type User struct {
Id: string `gorm:"primaryKey"`
}
// Note: Gorm will fail if the function signature
// does not include `*gorm.DB` and `error`
func (user *User) BeforeCreate(tx *gorm.DB) (err error) {
// UUID version 4
user.Id = uuid.NewString()
return
}
Notes:
For the Google UUID package, the methods uuid.New() and uuid.NewString() use UUID version 4. This is not clearly stated in the documentation (http://pkg.go.dev/github.com/google/uuid), but by looking into the source code, you can see that these are wrappers around uuid.NewRandom() which is stated as being UUID version 4.
While some recommend the Satori UUID package (https://github.com/satori/go.uuid), benchmarks show that it has 3.3x lower performance than the Google UUID package
(https://gist.github.com/mattes/69a4ab7027b9e8ee952b5843e7ca6955)
The error (pq: relation "users" does not exist) usually means that, the table users does not exists in the database. It has nothing to do with the relationship between two models.
So basically, You first need to create the table in the database (Or auto migrate the database As per #Apin suggest). And try to re-run the same code.
None of these worked for me using gorm v1.21.
Here was my solution. Note that I'm using the satori/go.uuid library for generating UUID, but code with google's library is near identical.
type UUIDBaseModel struct {
ID uuid.UUID `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
}
func (base *UUIDBaseModel) BeforeCreate(tx *gorm.DB) error {
uuid := uuid.NewV4().String()
tx.Statement.SetColumn("ID", uuid)
return nil
}
Now I used Gorm 2.0 and this worked:
go get github.com/satori/go.uuid
type Tablename struct {
ID string `sql:"type:uuid;primary_key;default:uuid_generate_v4()"`
}

`gorm` Ignoring `sql:"index"` Tags

Why gorm is ignoring sql:"index" tags? No indexes got created.
Database in use here is PostgreSQL (importing _ "github.com/lib/pq"). This Model struct is used (because default gorm.Model uses an auto increment number - serial - as primary key and I wanted to set id myself):
type Model struct {
ID int64 `sql:"type:bigint PRIMARY KEY;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
And one of actual models is:
type TUHistory struct {
Model
TUID int64 `json:"tu_id,string" gorm:"column:tu_id" sql:"index"`
}
func (x *TUHistory) TableName() string {
return "tu_history"
}
And the table is created by db.CreateTable(&TUHistory{}) which creates the table correctly except for indexes.
As a temporary work around, I do db.Model(&TUHistory{}).AddIndex("ix_tuh_tu_id", "tu_id") to create indexes.
From my experience, the db.CreateTable only creates the table and it's fields. You are better off using the AutoMigrate function with the model structure that you want to migrate:
db, err := gorm.Open("postgres", connectionString)
...
// error checking
...
db.AutoMigrate(&Model)
Also, I tried AutoMigrating the model you posted and got an error saying that multiple primary keys are not allowed, so I changed the model to:
type Model struct {
Id int64 `sql:"type:bigint;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
and the AutoMigration created all PKs and indexes just fine.
Edit:
Checking the GORM's README, on this example, the Email structure goes as:
type Email struct {
ID int
UserID int `sql:"index"` // Foreign key (belongs to), tag `index` will create index for this field when using AutoMigrate
Email string `sql:"type:varchar(100);unique_index"` // Set field's sql type, tag `unique_index` will create unique index
Subscribed bool
}
Notice the comment on the UserId field saying it will create the index when using AutoMigrate.
Also, it's worth taking a look at how the AutoMigrate does it's job:
// Automating Migration
db.AutoMigrate(&User{})
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// Feel free to change your struct, AutoMigrate will keep your database up-to-date.
// AutoMigrate will ONLY add *new columns* and *new indexes*,
// WON'T update current column's type or delete unused columns, to protect your data.
// If the table is not existing, AutoMigrate will create the table automatically.