Preload can't find field fieldName in *models.Catalog - go

I can't find an answer to my question. I am using jinzhu/gorm in a golang project:)
I have the following structs:
type Catalog struct {
ID int64 `gorm:"primary_key" form:"id"`
SubDomainID int64 `form:"sub_domain_id"`
ServiceTypeID int64 `form:"service_type_id"`
Checked bool `form:"checked"`
CreatedAt time.Time `form:"created_at"`
UpdatedAt time.Time `form:"updated_at"`
SubDomain SubDomain
}
type SubDomain struct {
Id int64 `gorm:"primary_key" form:"id"`
NameRu string `form:name_ru`
url string `form:url`
}
When I try to get catalog with preloading of subdomain:
var catalog Catalog
fmt.Println(catalog.SubDomain)
err := db.Preload("SubDomain").Where("checked = 0").First(&catalog).Error
if err != nil {
return &catalog, err
}
I get the following error: can't find field SubDomain in *models.Catalog
Why is this happening?
I expect there will be 2 queries:
select * from catalogs where checked = 0;
select * from sub_domains where id = (catalog.sub_domain_id)

I'm still new to gorm, but I think I know your problem and also have a (partial) solution.
As stated before (and you said so yourself), when applying "select * from..." it also looks for the field SubDomain (because it is in your struct).
So I believe this should work:
var catalog Catalog
fmt.Println(catalog.SubDomain)
err := db.Preload("SubDomain").Select("ID","SubDomainID", "ServiceTypeID").Where("checked = 0").First(&catalog).Error
if err != nil {
return &catalog, err
}
note how i specified the exact fields. A better solution will be to write a function to exclude member which are fields, using reflection. I am using a similar solution myself. it kinda looks like this:
for each member of Domain:
if member is string or boolean
fields.append(member).
return db.Select(fields) // actual gormdb's Select

Related

How to do a many-to-many query

I'm trying to understand how to use GORM to make query on items with many2many relations but I'm really lost.
I've got the following database model:
type Asset struct {
gorm.Model
Id uint `gorm:"primaryKey"`
MachineUID string `gorm:"type:varchar(128)" json:"machine_uid"`
AssetToken string `gorm:"uniqueIndex;type:varchar(128)"`
CommandQueries []*CommandQuery `gorm:"many2many:command_asset;"`
}
type CommandQuery struct {
gorm.Model
Id uint `gorm:"primaryKey"`
UUID string `gorm:"type:varchar(128)" json:"uuid"`
CmdType int `json:"cmdtype"`
CmdArgs string `gorm:"type:varchar(128)" json:"cmdargs"`
Assets *[]Asset `gorm:"many2many:command_asset;"`
Active bool
}
First, i'm successfully trying to retrieve an asset from a token with something like this:
token := "test-token"
var result Asset
db.Where("asset_token = ?", token).First(&result)
if result.Id == 0 {
return fmt.Errorf("Asset cannot be found in database")
}
But fom this returned struct, i would like to retrive all CommandQuery objects where:
this asset is in CommandQuery.assets
Where CommandQuery.active = true
I tried many things but nothing works, any help would be appreciated.
If I understood correctly, you want to load a slice of CommandQuery objects, and these objects should contain only assets where asset_token should be equal to the token you passed. Also, return only CommanQuery objects that have active=true.
If this is the case, it can be done like this:
token := "test-token"
var list []CommandQuery{}
tx := db.Preload("Assets", func (gdb *gorm.DB) *gorm.DB{
return gdb.Where("asset_token = ?", token)
}).
Where("active = ?", true).Find(&list)
if tx.Error == nil {
return nil, tx.Error
}
return *list, nil
In short, custom preloading is used to load assets into command query objects.

How to set column value as false

I want to update a column "enabled" to false using Gin framework in Go. I know that it doesn't allow the column to be set as false by default, so I wrote the below code to handle that.
if err := db.Model(&subscription).Update(map[string]interface{}{"enabled": false}).Error; err != nil {//do something}
Somehow, this doesn't seem to be working anymore. What is wrong in this code?
This is my model:
type Subscription struct {
gorm.Model
Enabled bool `gorm:"DEFAULT:True"`
Deleted bool `gorm:"DEFAULT:False"`
UserID uint `gorm:"not null"`
SubscriptionTypeID uint `gorm:"not null"`
Cap int `gorm:"DEFAULT:-1"`
DateOfMonth string `gorm:"DEFAULT:'0'"`
}
You can use the Updates method to do it in the way you have tried. This also works for multiple columns.
if err := db.Model(&subscription).Updates(map[string]interface{}{"enabled": false}).Error; err != nil {//do something}
Also, you can use the Update method to update the single column.
if err := db.Model(&subscription).Update("enabled", false).Error; err != nil {//do something}
Here you can find more variations on how to update a single column or multiple columns.
Ankita Gupta, You should declare you model like this:
type MyModel struct {
gorm.Model
Enabled bool `gorm:"default:false"`
}
In this way the boolean default value is false

"reflect: Field index out of range" panic on update

I get a panic implementing a basic update with Gorm and I couldn't find any information about it. Following Gorm's documentation, I'm not looking like doing wrong.
module achiever
go 1.15
require (
gorm.io/driver/postgres v1.0.5
gorm.io/gorm v1.20.7
)
type Task struct {
ID uint `json:"id" gorm:"primary_key"`
Title string `json:"title"`
}
type UpdateTaskInput struct {
Title string `json:"title"`
}
func UpdateTask(id uint, input UpdateTaskInput) bool {
var task Task
if err := models.DB.Where("id = ?", id).First(&task).Error; err != nil {
return false
}
models.DB.Model(&task).Updates(input)
return true
}
reflect: Field index out of range
/usr/local/go/src/reflect/value.go:854 (0x4bf758)
Value.Field: panic("reflect: Field index out of range")
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/schema/field.go:388 (0x57b984)
(*Field).setupValuerAndSetter.func1: fieldValue := reflect.Indirect(value).Field(field.StructField.Index[0])
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:230 (0x881670)
ConvertToAssignments: value, isZero := field.ValueOf(updatingValue)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks/update.go:64 (0x87f259)
Update: if set := ConvertToAssignments(db.Statement); len(set) != 0 {
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/callbacks.go:105 (0x5a65bc)
(*processor).Execute: f(db)
/home/raphael/gows/pkg/mod/gorm.io/gorm#v1.20.7/finisher_api.go:303 (0x5ae2c6)
(*DB).Updates: tx.callbacks.Update().Execute(tx)
/home/raphael/ws/achiever/server/controllers/tasks.go:74 (0xad62cd)
UpdateTask: models.DB.Model(&task).Updates(input)
I can't figure out where the error is coming from.
I was stuck with the same problem, as jugendhacker says: you need to construct the model's struct inside the update function:
models.DB.Model(&task).Updates(&Task{Title: input.Title})
This is stated in the GORM docs, if you are building an API, it would be more adequate to implement this as a PUT method, as you are passing a new object instead of patching an existing one.
Adding ID field to the UpdateTaskInput struct can fix this issue
type UpdateTaskInput struct {
ID uint `json:"-"`
Title string `json:"title"`
}
As far as I know the Update does not support "Smart Select Fields" so you need to construct the original struct first.
models.DB.Model(&task).Updates(&Task{Title: input.Title})

Gorm query returning only a single row

We're trying to use Gorm with mysql 8 to much frustration.
I have the following tables (simplified for brevity here)
type StoragePool struct {
gorm.Model
PoolId string `json:"id" gorm:"column:poolid;size:40;unique;not null"`
Volumes []Volume `json:"volumes" gorm:"foreignkey:StorageId;association_foreignkey:PoolId"`
}
type Volume struct {
gorm.Model
StorageId string `json:"storageid" gorm:"column:storageid;size:40"`
}
Data insertions seem to work fine. Both tables get populated and no constraints are violated.
A query that expects a single record seems to work fine:
poolRecord := &StoragePool{}
if err := tx.Where("poolid = ?", pool.PoolId).First(&StoragePool{}).Scan(poolRecord).Error; err != nil {
return err
}
This query only returns a single row. When I perform this exact query as raw SQL outside of go, it returns all 31 records I expect.
var poolVolumes []Volume
if err := tx.Where("storageid = ?", pool.PoolId).Find(&Volume{}).Scan(&poolVolumes).Error; err != nil {
return err
}
log.Debugf("found %d volumes belonging to %q [%s]", len(poolVolumes), pool.Name, pool.PoolId)
According to the docs, that second sql statement is the equivalent of "SELECT * FROM VOLUMES WHERE STORAGEID = 'poolid'". That is not the behavior I am getting.
Anyone have any ideas what I'm doing wrong here?
I rarely use an ORM while coding with go, but following the doc from gorm, it seems like you are doing it the wrong way.
Scan is used for scanning result into another struct, like this:
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name, age").Where("name = ?", 3).Scan(&result)
The correct way to get query results into a slice of structs should be:
var poolVolumes []Volume
if err := tx.Where("storageid = ?", pool.PoolId).Find(&poolVolumes).Error; err != nil {
return err
}

Designing a Datastore schema for a page that users can edit (e.g. a wikipedia/stackoverflow page)

The idea is to design a table/entity that contains some basic info, as well as a Markdown-Content field that would allow users to easily create tables and such.
I'm thinking something like this:
type Tournament struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
Name string
URL string
DateCreated int64
CreatedBy string
DateUpdated int64
UpdatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
My problem is figuring out how to update it. The ID field will also be used as the URL path, e.g. if an entity has ID 7 then it will be displayed on example.com/tournament/7.
I believe this eliminates the possibility of simply creating a new entity with updated data, and then set the ApprovalStatus=3 on the previous entity, because if you do as such then the example.com/tournament/7 URL will no longer request the correct ID.
I also don't like the idea of creating my own unique ID because I think it would be great to simply take advantage of the Datastore ID generation (which also makes it easy to get the correct entity based on URL); I considered creating a new entity/table that would keep track of revisions but I'm not sure how efficient all of this is, so I was hoping some expert might be able to give some advice.
Update related to #mkopriva solution:
If you do it this way, then it's necessary to include a TournamentID field inside the TournamentEdit entity struct I think?
type TournamentEdit struct {
ID in64 `datastore:"-"`
TournamentID int64
MDContent []byte `datastore:",noindex"`
DateCreated int64
CreatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
And then the retrieve function could look like this:
func (db *datastoreDB) GetTournamentByKeyID(ctx context.Context, keyID int64) (*Tournament, error) {
key := datastore.IDKey("Tournament", keyID, nil)
var tournamnet Tournament
err := db.client.Get(ctx, key, &tournament)
// err checking
tournament.ID = key.ID
var edits TournamentEdits
query := datastore.NewQuery("TournamentEdit")
query = query.Filter("TournamentID =", tournament.ID)
query = query.Filter("ApprovalStatus =", 1)
if _, err := db.client.GetAll(ctx, query, &edits); err != nil {
//err checking
}
tournament.Edits = edits // I guess this is wrong way to do it?
return &tournament, nil
}
Would this work?
One thing you could do is to simply create a new entity that would represent the edit of a tournament. By the way, I'm not a datastore user so I'm not sure if this is how you would model the entities but the general idea is the same for most, if not all, databases:
type Tournament struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
Name string
URL string
DateCreated int64
CreatedBy string
DateUpdated int64
UpdatedBy string
Edits []TournamentEdit
}
type TournamentEdit struct {
ID in64 `datastore:"-"`
MDContent []byte `datastore:",noindex"`
DateCreated int64
CreatedBy string
ApprovalStatus int64 // 0=to be decided, 1=approved, 2=rejected, 3=discontinued
ApprovalBy string
}
This should allow you to have multiple edits from different users in the queue, CRUD a specific edit, and or filter edits by their status.

Resources