Golang gorm preloading - go

I'm writing my first app in golang, so sorry for newbie question, but I wasn't able to find the solution for the following problem:
I have two tables, position and attachment. Each position can have multiple attachments. Here is my model:
type Positions struct {
Sys_id int `gorm:"AUTO_INCREMENT" gorm:"column:sys_id" json:"sys_id,omitempty"`
Name string `gorm:"size:120" gorm:"column:name" json:"name,omitempty"`
OpenPositions int `gorm:"column:open_positions" json:"open_positions,omitempty"`
ContactList string `gorm:"size:1000" gorm:"column:contact_list" json:"contact_list,omitempty"`
Attachments []Attachment `gorm:"ForeignKey:RecordId"`
}
type Attachment struct {
Sys_id int `gorm:"AUTO_INCREMENT" gorm:"column:sys_id" json:"sys_id"`
Name string `gorm:"size:255" gorm:"column: name" json:"name"`
File string `gorm:"size:255" gorm:"column:file" json:"file"`
RecordId int `gorm:"column:record_id" json:"record_id"`
Table string `gorm:"size:255" gorm:"column:table" json:"table"`
// ...
}
I want to query the db and get positions with attachments
positions2 := []models.Positions{}
err := db.Where("open_positions > ?", 0).Preload("Attachments", "`table` = ?", "user_position").Find(&positions2)
if err != nil {
log.WithFields(log.Fields{
"type": "queryerr",
"msg": err,
}).Error("faked up query")
}
Result of this query - I get positions correctly but the attachments are empty.
(can't preload field Attachments for models.Positions)
level=error msg="faked up query" msg=&{0xc04200aca0 can't preload field Attachments for models.Positions 6 0xc042187e40 0xc042187d90 0xc0422cd4a0 0 {0xc042225130} false map[] map[] false}
Thanks in advance for help

Models in your example have custom primary column names.
So, when only ForeignKey set for "has_many" association, Gorm trying to find Position's column Attachment.RecordId refers to.
By default it uses Position as prefix and Id as column name for this. But neither RecordId column has prefix Position nor Position model has column Id, so it fails.
In such case for "has_many" association you should specify both Foreign Key and Association Foreign Key.
In your example Association Foreign Key is the Position.Sys_id column and Attachment.RecordId refers to it.
So it should be fixed just by adding Association foreign key:
Attachments []Attachment `gorm:"ForeignKey:RecordId;AssociationForeignKey:sys_id"`

Looks like it is not about Go or Gorm but about SQL.
W3SCHOOLS:
A FOREIGN KEY in one table points to a PRIMARY KEY in another table.
But RecordId is not a primary key in your model. Let the foreign key refer to a primary key. It should be fixed with:
RecordId int `gorm:"column:record_id" gorm:"primary_key" json:"record_id"`

Related

many to many in gorm v2 error on foreign key

I'm finding it difficult to define many to many relationship using Gorm in following cases
features(feature_id, name, slug)
operations(operation_id, name, slug)
feature_operations(feature_id, operation_id)
type Feature struct {
FeatureID int64 `gorm:"primaryKey;column:feature_id" json:"feature_id"`
Name string `validate:"required" json:"name"`
Slug string `json:"slug"`
Status string `json:"status"`
Operations []Operation `gorm:"many2many:feature_operations;foreignKey:feature_id"`
appModels.BaseModel
}
When using feature_id, I get error
column feature_operations.feature_feature_id does not exist
When using id, I get error
invalid foreign key: id
Looks like you are not using the convention that gorm suggests where you name your primary key columns just id
so in your case your foreignKey should be the name of the field and you also need to use References to specify column that you want to reference. See the example here:
https://gorm.io/docs/many_to_many.html#Override-Foreign-Key
What you need is this:
type Feature struct {
FeatureID int64 `gorm:"primaryKey;column:feature_id"`
Name string
Slug string
Operations []Operation `gorm:"many2many:feature_operations;foreignKey:FeatureID;References:OperationID"`
}
type Operation struct {
OperationID int64 `gorm:"primaryKey;column:operation_id"`
Name string
Slug string
}
After this the join table will be FEATURE_OPERATIONS with two columns FEATURE_FEATURE_ID AND OPERATION_OPERATION_ID
If you dont like the redundant column names then you need to use the two additional attributes joinForeignKey and joinReferences to choose your own names for the columns like so:
gorm:"many2many:feature_operations;foreignKey:FeatureID;joinForeignKey:FeatureID;References:OperationID;joinReferences:OperationID"
All this extra work is needed because your primary keys are FEATURE_ID and OPERATION_ID instead of just ID
If you can rename the column to follow the convention, you will notice life is much easier with gorm

Group by column and scan into custom struct

I have the following model. Each instance is part of a group which is only defined as the string GroupName here because the actual group is defined in a different service using a different database.
type Instance struct {
gorm.Model
UserID uint
Name string `gorm:"index:idx_name_and_group,unique"`
GroupName string `gorm:"index:idx_name_and_group,unique"`
StackName string
DeployLog string `gorm:"type:text"`
Preset bool
PresetID uint
}
I'd like to scan, the above model, into the following struct. Thus grouping instances why their group name.
type GroupWithInstances struct {
Name string
Instances []*model.Instance
}
I'm been trying my luck with the following gorm code
var result []GroupWithInstances
err := r.db.
Model(&model.Instance{}).
Where("group_name IN ?", names).
Where("preset = ?", presets).
Group("group_name").
Scan(&result).Error
indent, _ := json.MarshalIndent(result, "", " ")
log.Println(string(indent))
But I'm getting the following error
ERROR: column "instances.id" must appear in the GROUP BY clause or be used in an aggregate function (SQLSTATE 42803)
I'm not sure how to deal with that since I don't want to group by instances but rather their groups.
The error indicates that your RDBMS is in Full Group By Mode - cannot select field that isn't in group by clause or used in an aggregate function (SUM, AVG,...). There are 2 solutions:
Disable Full Group By mode. Example in MySQL
Modify the query
Even when we go with Solution 1, gorm will throw another error about relationship between GroupWithInstances and Instance.
So I think we should review the feature and go with Solution 1 - only select what is needed.

"Creation At" time in GORM Customise Join table

I am trying to customize many2many table join. I have two tables from which I want to have taken the ids and want another field, which will tell me when the entry in the join table was made. The ids are coming fine, but the "created_at" is not updating and shows "Null" instead of time.
// this is the table join struct which I want to make
type UserChallenges struct {
gorm.JoinTableHandler
CreatedAt time.Time
UserID int
ChallengeID int
}
//hook before create
func (UserChallenges) BeforeCreate(Db \*gorm.DB) error {
Db.SetJoinTableHandler(&User{}, "ChallengeId", &UserChallenges{})
return nil
}
This is not giving any error on the build. Please tell me what I am missing so that I can get the creation time field in this.
PS - The documentation of GORM on gorm.io is still showing SetupJoinTable method but it is deprecated in the newer version. There is a SetJoinTableHandler but there is no documentation available for it anywhere.
The thing to get about using a Join Table model is that if you want to access fields inside the model, you must query it explicitly.
That is using db.Model(&User{ID: 1}).Association("Challenges").Find(&challenges) or db.Preload("Challenges").Find(&users), etc. will just give you collections of the associated struct and in those there is no place in which to put the extra fields!
For that you would do:
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Find(&joins)
// now joins contains all the records in the join table pertaining to user.ID,
// you can access joins[i].CreatedAt for example.
If you wanted also to retrieve the Challenges with that, you could modify your join struct to integrate the BelongsTo relation that it has with Challenge and preload it:
type UserChallenges struct {
UserID int `gorm:"primaryKey"`
ChallengeID int `gorm:"primaryKey"`
Challenge Challenge
CreatedAt time.Time
}
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Joins("Challenge").Find(&joins)
// now joins[i].Challenge is populated

Save is trying to update created_at column

We are updating our project from v1 to v2.
When we try to update a row by providing only changed fields as a struct, it tries to set created_at column and returns an error. This was working back in v1. According to documentation, during update operations, fields with default values are ignored.
err := r.writeDB.Save(&Record{
Model: Model{
ID: 1,
},
Name: "AB",
}).Error
if err != nil {
return err
}
Generates the following SQL statement
[3.171ms] [rows:0] UPDATE `records` SET `created_at`='0000-00-00 00:00:00',`updated_at`='2020-11-12 15:38:36.285',`name`='AB' WHERE `id` = 1
Returns following error
Error 1292: Incorrect datetime value: '0000-00-00' for column
'created_at' at row 1
With these entities
type Model struct {
ID int `gorm:"primary_key,type:INT;not null;AUTO_INCREMENT"`
CreatedAt time.Time `gorm:"type:TIMESTAMP(6)"`
UpdatedAt time.Time `gorm:"type:TIMESTAMP(6)"`
}
type Record struct {
Model
Name string
Details string
}
There is DB.Omit which allows ignoring a column when executing an update query. But this requires a lot of refactoring in the codebase. Does the behavior changed or is there something missing?
This might help you. Change the structure field (or add to replace default gorm.Model field) like this:
CreatedAt time.Time `gorm:"<-:create"` // allow read and create, but don't update
This tag helps to save created data from update.
In Gorm v2 Save(..) writes all fields to the database. As the CreatedAt field is the zero value, this is written to the database.
For you code to work, you can use a map:
err := r.writeDB.Model(&Record{Model:Model{ID:1}}).Update("name","AB").Error
Another option is to not fill in the Record struct, but just point to the table and use a Where clause:
err := r.writeDB.Model(&Record{}).Where("id = ?", 1).Update("name","AB").Error
If you have multiple updates you can use Updates(...) with a map, see here: https://gorm.io/docs/update.html
I was able to work around this problem using Omit() before save. Like this:
result := r.db.Omit("created_at").Save(item)
It omits the CreatedAt from the resulting update query, and updates everything else.

Is there a way to add a custom column when we create a many2many association in gorm?

I want to know 2 things
I got the following structs from a similar question.
// models/school.go
type School struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null"`
Accreditations []Accreditation `gorm:"many2many:school_accreditation;"`
}
type Accreditation struct {
// "accreditation" table
ID int `gorm:"primary_key"`
Name string
Description string
}
So, by default this will create a school_accreditation table with 2 columns:
one will have School's ID
other will have Accreditation's ID
My questions:
What is the most efficient way to add another column to the school_accreditation table?
Let's say I want to have the Name field of Accreditation in the school_accreditation table.
2.1) How do I achieve this eg: school_accreditation will have school_id, accreditation_id, accreditation_name
For the 1st question,
It seems the only way of doing it by defining the SchoolAccreditation model & adding fields to it.
Also, it gives me more control over the relationship.
For the 2nd question, I am yet to find a way if there is any!
bost
For the 2nd,
You have to manually get the name from Accreditation & then add it to the column via the defined SchoolAccreditation model.

Resources