GORM Golang how to optimize this code - go

I use GORM in my project and I want to create something like DB admin page.
To load records I send GET with params:
category: "name", // database table name
On server I have the next code:
func LoadItems(db *gorm.DB, category string) interface{} {
var items interface{}
loadItems := func(i interface{}) {
err := db.Find(i).Error
if err != nil {
panic(err)
}
items = i
}
switch category {
case "groups":
var records []*models.Groups
loadItems(&records)
case "departments":
var records []*models.Departments
loadItems(&records)
case .....
........
}
return items
}
Is it possible to replace switch because I have 10 tables and after record editing I send new data to server, where I re forced to use switch in other function to save it.

I'm not familiar with gorm, but:
Maybe store "department" (as key) and a variable of the corresponding model type in a map, and then referencing via key to the model. If not already, the models then have to implement a common interface in order to be able to store them in one map.
Though, if that would be a better solution I'm not sure. Maybe a bit easier to maintain, because new model types only have to be added to the map, and you don't have to adjust switches on multiple places in your code.
Another obvious way would be, outsourcing the switch to a function, returning a variable of a common interface type and using that on different places in your code. That would definitely not be faster, but easier to maintain.

Related

populate struct fields in Go by looping over data from another function

I have a couple handlers in a web app (Fiber framework) where one handler retrieves data from an external API and the other handler takes a subset of this data and performs some business logic (ie sends a report, etc).
Both handlers are in the same package. In handler2.go I am able to dereference the data from handler1.go and I want to use specific values from that data to populate the struct fields in handler2.go. The dereferenced data from handler1.go is itself an array of structs that I can loop over.
In handler2.go , I have a struct:
type Report struct {
contact string
date string
resource string
}
// get data from handler1.go function and use it to populate the Report struct
// each "report" is a struct, so need to create a list of structs
func getReportData() {
reportData := GetReport() // call function in handler1.go
for _, report := range *reportData {
fmt.Println(report.Date)
}
So instead of simply printing the data (the print statement is just to show that I have access to the data I need) I want to populate the Report struct with specifc items from the data that I can can access using the loop and the report.<KEY> syntax.
How can I create a list of structs (using the Report struct) populated with the data I can get via this for loop?
For an MVP , I can simply format this list of structs (in json) and display an endpoint in the web app. I am just struggling with how to construct this data properly.
To answer the direct question, if we assume that the values returned by GetReport() have Date, Contact, and Resource fields, then you could write:
type Report struct {
contact string
date string
resource string
}
// Return a list (well, slice) of Reports
func getReportData() (reports []Report) {
reportData := GetReport()
for _, report := range reportData {
myReport := Report{
contact: report.Contact,
date: report.Date,
resource: report.Resource,
}
reports = append(reports, myReport)
}
return
}

How to get all fields in a response in graphql without passing any field names in a query

I'm building a graphql interface using golang. I'm using gqlgen package to implement it.
Here I need to pass all field names in a query to get it in response, But the problem is my data is huge, it is having more than 30 fields it would be difficult to pass all fields in a query.
This is my query
{Model{id, name, email, mobile,...............}}
Like this I need to pass all fields name.
Instead Im looking for a result which will return all fields without passing any fields. I mean if not passing any field names it should return all.
For example
{Model{}}
First, you really should list out all the fields in your query. That is the nature of graphql. It is verbose, but most client libraries get the fields from your data structure anyway, so it's not that bad.
So I recommend listing out all fields manually!
Using Scalars (must be on v0.11.3 or below, see https://github.com/99designs/gqlgen/issues/1293)
But if you insist, if there is a will, there is way. You can use GraphQL's scalar types and make your own. See this doc for how to make them with gqlgen: https://gqlgen.com/reference/scalars/
In your schema, you can make a JSON scalar:
scalar JSON
type Query {
random: JSON!
}
Make a model for this
// in your own models.go
// You can really play with this to make it better, easier to use
type JSONScalar json.RawMessage
// UnmarshalGQL implements the graphql.Unmarshaler interface
func (y *JSONScalar) UnmarshalGQL(v interface{}) error {
data, ok := v.(string)
if !ok {
return fmt.Errorf("Scalar must be a string")
}
*y = []byte(data)
return nil
}
// MarshalGQL implements the graphql.Marshaler interface
func (y JSONScalar) MarshalGQL(w io.Writer) {
_, _ = w.Write(y)
}
Then link the scalar to your custom type in the gql.yml
models:
JSON:
model: github.com/your-project/path/graph/model.JSONScalar
When you run the generate (use gqlgen v0.11.3 or below, gqlgen version), your resolvers will now use the custom type you made. And it's easy to use:
func (r *queryResolver) random(ctx context.Context) (model.JSONScalar, error) {
// something is the data structure you want to return as json
something := struct {
Value string
}{
Value: "Hello World",
}
d, _ := json.Marshal(something)
return model1.JSONScalar(d), nil
}
The resulting query of
// Query
{
random
}
// Response
{
"random" : {
"Value": "Hello World!"
}
}

Gorm many-to-many relationship duplicates values

I am trying to set up a many to many relationship in my demo API, between a Job, and a list of skills []Skill.
Job Struct
type Job struct {
ID string `sql:"type:uuid;primary_key;"`
Title string `json:"title,omitempty"`
Skills []*skill.Skill `json:"skills,omitempty"gorm:"many2many:job_skill;"`
CreatedAt time.Time
UpdatedAt time.Time
}
Skill Struct
type Skill struct {
gorm.Model
Name string `json:"name,omitempty"`
}
I then use gorm.DB.AutoMigrate() to generate the join table automatically.
When I send a POST request to my API, the data is created correctly the first time around, and the join table populates as you'd expect.
Example POST data
{
"title": "Senior Python Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}]
}
But then when I send a PATCH request to add a new skill, it duplicates the skill in the skills table and then creates a new record in the join table for the skills that already exist.
Example PATCH data
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
Doing a get request for the data will show the following:
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
I have also tried setting gorm:"unique" on the skill struck Name but when adding a new Skill it fails as it says the other two already exist, which is good but then won't add the new one.
I am assuming I can only send back new values? Not the entire list?
Some of my Go code for clarity
func GetJobs(w http.ResponseWriter, r *http.Request) {
j := &[]Job{}
o := database.DB.Preload("Skills").Find(&j)
render.JSON(w, r, o)
}
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
o := database.DB.Model(&j).Where("id = ?", j.ID).Update(&j)
render.JSON(w, r, o)
}
These are two things with gorm associations that I feel aren't adequately conveyed in its documentation which continue to confuse developers.
1) It uses IDs to identify the associated entities
When you Update that list of skills, if they have no IDs in them to gorm they are just new entities to be added. This leads to your duplicated values. Even if you have a unique field, if that field isn't the entity's primary key, then gorm will try to create a new record and result in a constraint violation instead.
There are a few ways of dealing with this:
Making sure the API user must supply an ID for the related entities
Pulling out of the DB the entity IDs via some other surrogate key that the user does provide, and populating those in your to-save entity. In your case that could be name since it's unique.
Making that surrogate key your primary key (making name the gorm:"primaryKey" of your Skill struct).
2) When Updating, it won't delete existing associations that don't appear in the association slice
When you call Save/Update gorm doesn't delete entities in the far side of collection associations. This is a safety feature to avoid accidentally deleting data on a simple Save/Update. You have to be explicit about wanting that behaviour.
To deal with that you can use Association mode to replace the collection as part of your update: db.Model(&job).Association('Skills').Replace(&job.Skills).
To elaborate on Eziquel's answer and show what worked for me.
I updated my Skill struct to use the Name as the primary key
type Skill struct {
Name string `json:"name,omitempty" gorm:"primary_key"`
}
I then read some documentation saing to just use gorm.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j) Not sure what it does, but without it, the associations were duplicating or not updating.
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
render.JSON(w, r, &j)
}
I dont know what database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j) does but I saw Jihnzu say to use it in the docs, no further explanation. Using that in combination with the .Association("Skills").Replace(&j.Skills) ensure there are no duplicates and that the association updates.
If there was more than one you would do:
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
database.DB.Model(&j).Association("Locations").Replace(&j.Locations)

Simplest way to detect if any model value has changed

I'm using gorm as a Golang ORM. I need to detect if any model field has changed to trigger a update on a thirdy-party API service.
I have tried to test every field with an If statement, but it gets ugly when the model has many fields.
var person Person
db.Where("id = ?", id).First(&person)
if person.Name != body.Person.Name || person.Age != body.Person.Age {
// Trigger API update
}
db.Save(&person)
Is there a easy way to achieve this?
I don't know if this is the simplest way, and it is probably not idiomatic, but you can accomplish this with reflection. The following function uses the reflect Package to compare two Person structs to see if their values for each field are the same, skipping the Model struct (whose internals vary independently of the data element the Person represents).
func (this Person) Equals(that Person) bool {
vThis := reflect.ValueOf(this)
vThat := reflect.ValueOf(that)
for i := 0; i < vThis.NumField(); i++ {
if vThis.Field(i) != vThis.FieldByName("Model") {
if vThis.Field(i).Interface() != vThat.Field(i).Interface() {
return false
}
}
}
return true
}
You could use this in your code snippet then as follows:
if !person.Equals(body.Person) {
// Trigger API update
}
Note that I'm quite new to go, so I may be leading you astray here in terms of "proper" code. But this does work.
GORM does provide such a feature via the 'Changed' method which could be used in Before Update Hooks. It returns if the field was changed or not.
https://gorm.io/docs/update.html#Check-Field-has-changed

When avoiding global vars (/state), i find myself linking objects backwards to its parent. Am I doing this right? if not explain why? and how-else?

Note: Im just picking the current struct/example to explain the problem.
type MsgBoxFactory struct{
db *dbSql //contains conn-pool and other DB related settings/flags
}
func (f *MsgBoxFactory) NewMsgBox(userId string) {
return MsgBox{userId, f.db} //f.db link is inevitable
}
type MsgBox struct {
ownerId string
db *dbSql
}
func (m *MsgBox) NewMessage(content string) *Message {
return Message{content, *m.dbSql} //m.dbSql link is inevitable
}
type Message struct {
content string
//other fields such as recipents, isRead, created time etc.
db *dbSql
}
func (m *Message) Send(to string) {
message.to = to //just imagine this saves the message to database.
m.db.Save(message)
}
I tend to call this "backward-referencing"[i don't know actual name]... Is this the only way? Previously i was "backward-referencing" entire parent objects. Now i find myself "backward-referencing" objects such as config/dbconn etc...
Is this a good way? what is better?
Oh i have also tried closure to get rid of it atleast from view.
type Message Struct{
content string
Send func(string) error // the problem is `json:"-"` needs to be added. Else the objects are not json compatible
}
func (m *MsgBox) NewMsg(content string) *Message {
msg := &Message{ content }
msg.Send = func(to string) error {
return m.db.Save(msg)
}
}
Basically the code looks almost equally cluttered with unnecessary complexity/code
EDIT: The question is not specific to go. Just posted it because i use go. Any tag suggestion is appreciated to open the question for wider community.
I usually implement a model helper relationship.
Where MsgBox is your model which has all the data specific elements (No DB related elements).
The MsgBoxHelper does all your database related work.
(i.e.
err := MsgBoxHelper.Save(MsgBox)
msgBox, err := MsgBoxHelper.Load(Key)
)
Edit:
The advantage with this method is it decouples your Model from the datastore, which should inturn make it easier should you wish to change your underlying technology (Which doesn't often happen). In practice it's more useful should you start doing things like caching.
If generically you are referencing other structures within your model i.e.
type MsgBox struct {
Colour *MsgBoxColour
...
}
type MsgBoxColor struct {
ID int
...
}
then when you load the Model in your MsgBoxHelper you call the MsgBoxColourHelper with the ID you stored in the MsgBoxColour table, this then returns your MsgBoxColour that you then associate with the returning MsgBox.

Resources