GORM updating null field when calling Updates()? - go

According to GORM's docs:
Updates supports update with struct or map[string]interface{}, when
updating with struct it will only update non-zero fields by default
I have an entry in my database already for the Service with ID, abc123. I am attempting to take an object that looks like the one below:
Service{
ID: "abc123",
Name: "new service name",
CreatedAt: nil,
}
And use it to update the my existing record. But when I call:
tx.Model(&service).Updates(service)
the CreatedAt value in the database is overwritten with nil. How can I update my database record without overwritting the CreatedAt value?
Update: Below is my Service struct
type Service struct {
ID string `gorm:"not null;type:char(32);primary_key;column:id"`
Name string `json:"name" gorm:"size:50;not null;"`
CreatedAt *time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
}
I've tried two different variations for my Service struct. the other is with CreatedAt being of type time.Time instead of *time.Time. With *time.Time it will overwrite the value in my DB with a null value. With time.Time it attempts to overwrite the value in the DB with an uninitialized time value and throws the error: Error 1292: Incorrect datetime value: '0000-00-00' for column 'created_at' at row 1

By default, gorm will not update default(zero) or nil values.
If you want to have control over it use something as I described below.
You can use nullable types provided by sql package or create your own.
type Model struct {
Amount *sql.NullFloat64
}
// amount will not be updated
gorm.Updates(Model{Amount: nil})
// amount will be updated as a null
gorm.Updates(Model{Amount: &sql.NullFloat64{}})
// amount will be updated as a 10.50
gorm.Updates(Model{Amount: &sql.NullFloat64{Float64: 10.50, Valid: true}})

A zero value, or a default value, for a time.Time field type inside a struct is time.Time{}. When using Updates, either don't populate the CreatedAt field, or assign time.Time{} value to it.
In the example below, the default or zero value is printed out for CreatedAt field in both cases.
package main
import (
"fmt"
"time"
)
type T struct {
CreatedAt time.Time
C int
S string
}
func main() {
fmt.Println(T{C: 1, S: "one"})
fmt.Println(T{C: 2, S: "two", CreatedAt: time.Time{}})
}
// {0001-01-01 00:00:00 +0000 UTC 1 one}
// {0001-01-01 00:00:00 +0000 UTC 2 two}
EDIT:
Also, I'm not sure how even CreatedAt: nil, compiles if the CreatedAt field is of time.Time type, and not *time.Time.
Since you've updated the Service struct and CreatedAt field type to *time.Time, following should work:
tx.Model(&service).Updates(Service{Name: service.Name}) // add all fields that you want to be updated.
// resulting query
// UPDATE services SET name = 'new service name' WHERE id = 'abc123';
An official GORM example is here
Additionally, you can omit the created_at field like this:
tx.Model(&service).Omit("created_at").Updates(service)

use map
https://gorm.io/docs/update.html#Updates-multiple-columns
tx.Model(&service).Updates(map[string]interface{}{"ID": "abc123", "Name": "new service name", "CreatedAt": nil})

Related

GO: import a struct and rename it in json

I have built a database in go with gorm. For this I created a struct and with this struct I created a table. So far so good. In the backend everything works, but in the frontend the problem is that the JSON which is called always returns the ID in upper case and swagger generates me an ID which is lower case. Is there a way in Go that I can overwrite the imported struct from gorm with a JSON identifier?
import "gorm.io/gorm"
type Report struct {
gorm.Model
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
This Struct gives me the following response
{
"ID": 8,
"CreatedAt": "2022-11-15T20:45:16.83+01:00",
"UpdatedAt": "2022-12-27T21:34:17.871+01:00",
"DeletedAt": null
"createdBy": {
"ID": 1,
"CreatedAt": "2022-11-15T20:02:17.497+01:00",
"UpdatedAt": "2022-11-15T20:02:17.497+01:00",
...
},
"archived": true,
}
Is there a way to make the ID lowercase (like Archived)? Or can I adjust it at swaggo so that it is generated in upper case.
What I have seen is that you can make the table without this gorm.Model and define all the attributes yourself. The problem is that I then have to create all the functionalities (delete, update, index, primary key, ...) of these columns myself.
You can use the mapstructure package.
mapstructure is a Go library for decoding generic map values to structures and vice versa
If you want to embed ID field from gorm.Model with custom json tag, and you want it to be on the same struct and not in a "substruct", you can use mapstructure:",squash" tag on the embedded model.
type Model struct {
ID uint `json:"id"`
}
type Report struct {
Model `mapstructure:",squash"`
Archived bool `json:"archived"`
}
func main() {
input := map[string]interface{}{
"id": 1,
"archived": false,
}
var report Report
if err := mapstructure.Decode(input, &report); err != nil {
panic(err)
}
fmt.Println("Report ID:", report.ID)
fmt.Println("Report ID via Model:", report.Model.ID)
}
As you can observe, with mapstructure.Decode method you can convert map to struct with the squash option, and you can then access ID of report directly. Note that you can still access report.Model and all its fields.
With mapstructure, you can make the ID lowercase as you wanted, and also accessable from the report struct directly, not only from a Model substruct.
I create my own gorm-model-struct:
type GormModel struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
} //#name models.gormModel
I import this struct into the other structs:
type Report struct {
GormModel
CreatedBy User `gorm:"foreignKey:CreatedByUserID" json:"createdBy"`
Archived bool `json:"archived"`
}
Important is, that you add the json-key and set the attribute name.

Gorm Timestamps in Golang

type Base struct {
ID uint `gorm:"primary_key" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *gorm.DeletedAt `sql:"index" json:"deleted_at" swaggertype:"primitive,string"`
CreatedByID uint `gorm:"column:created_by_id" json:"created_by_id"`
UpdatedByID uint `gorm:"column:updated_by_id" json:"updated_by_id"`
}
If I pass some values to created_at and updated_at it is taking up the current time as a default value and not taking up the value that I have passed. Is there any way I can make the gorm take up the values that I have passed.
Three ways
Define default tag for field with a database function to fill in the default. e.g. for MySQL database current_timestamp() can be used
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP()"`
Assign default values as suggested by #Guolei.
Embed gorm.Model in your struct instead, for automatic handling of ID, CreatedAt, UpdatedAt and DeletedAt.
if containing fileds: CreatedAt and UpdatedAt, gorm will use reflect to insert default value when executing.
also you could give the specific value.
info := Example{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err := DB.Insert(info)
An update to #s3vt's answer:
If you the first method they provided, you need to put the function without the parenthesis like this:
CreatedAt time.Time `gorm:"default:current_timestamp"`
I spent a lot of time trying to figure out the error I was getting so I thought I'd share it for the people who might face this!

Merging 2 structures with different types

I have two structures.
EventForm is the structure that is used to parse the POST body of a request.
EventTable is used for creating the MYSQL table structure and finding/creating rows.
I want to merge EventForm with EventTable so that fields like ID cannot be overridden via the POST body. I am not able to convert the type of EventForm to EventTable since you cannot convert a struct to a different type if the fields do not 100% match. So my question is what is the best way to merge these two structs? If it is not plausible to merge these two structs how could I best solve this problem?
package models
import "time"
// EventTable table structure of "events"
type EventTable struct {
EventForm `xorm:"extends"`
ID int `xorm:"autoincr pk 'id'" json:"id"`
Created time.Time `xorm:"not null created" json:"created"`
Updated time.Time `xorm:"not null updated" json:"updated"`
}
// TableName table name of EventTable
func (u *EventTable) TableName() string {
return "events"
}
// EventForm the structure that is received via an API call
type EventForm struct {
Title string `xorm:"not null" json:"title" required:"true"`
Description string `xorm:"not null" json:"description" required:"true"`
Owner string `xorm:"not null" json:"owner" required:"true"`
Lat string `xorm:"not null" json:"lat" required:"true"`
Lng string `xorm:"not null" json:"lng" required:"true"`
}
I am with #mkopriva and don't fully understand what the problem is. Assuming you are receiving EventForm from some API call
evtForm := GetSomeEventForm()
evtTable := &models.EventTable{ EventForm: evtForm, Created: time.Now() }
someORMProbably.Insert(evtTable)

Golang imported fields do not act the same as standard field declarations

I'm going to try to simplify the problem down rather than bring the whole project into scope so if you have questions I'll try to update with more information.
I have 3 structs I'm working with:
type Ticket struct{
ID bson.ObjectID `json:"id" bson:"_id"`
InteractionIDs []bson.ObjectId `json:"interactionIds" bson:"interactionIds"`
TicketNumber int `json:"ticketNumber" bson:"ticketNumber"`
Active bool `json:"active" bson:"active"`
// Other fields not included
}
type TicketInteraction struct{
ID bson.ObjectID `json:"id" bson:"_id"`
Active bool `json:"active" bson:"active"`
// Other fields not included
}
type TicketLookupTicket struct{
Ticket
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
Then I have an mgo pipe that I'm working with to 'join' the two collections together
var tickets []TicketLookupTicket
c := mongodb.NewCollectionSession("tickets")
defer c.Close()
pipe := c.Session.Pipe(
[]bson.M{
"$lookup": bson.M{
"from": "ticketInteractions",
"localField": "interactionIds",
"foreignField": "_id",
"as": "interactions",
}
},
)
pipe.All(&tickets)
Now tickets should be populated with the result from the database, but what is actually happening is only the interactions within each ticket has been populated. It ends up looking something like this:
[
{
ID: ObjectIdHex(""),
InteractionIDs: nil,
TicketNumber: 0,
Active: false,
// Other Fields, all with default values
Interactions: []{
{
ID: ObjectIdHex("5a441ffea1c9800148669cc7"),
Active: true,
// Other Fields, with appropriate values
}
}
}
]
Now if I manually declare some of the Ticket structs fields inside the TicketLookupTicket struct, those fields will start populating. Ex:
type TicketLookupTicket struct{
Ticket
ID bson.ObjectId `json:"id" bson:"_id"`
TicketNumber int `json:"ticketNumber" bson:"ticketNumber"`
Active bool `json:"active" bson:"active"`
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
Now ID, TicketNumber, and Active will start populating, but the remaining fields won't.
Can anyone explain why the imported Ticket fields aren't behaving the same as the declared fields?
Per the documentation, you need to add the inline tag to the field:
type TicketLookupTicket struct{
Ticket `bson:",inline"`
Interactions []TicketInteraction `json:"interactions" bson:"interactions"`
}
inline Inline the field, which must be a struct or a map.
Inlined structs are handled as if its fields were part
of the outer struct. An inlined map causes keys that do
not match any other struct field to be inserted in the
map rather than being discarded as usual.
By default, it assumes that the embedded field Ticket should be filled by an object at TicketLookupTicket.Ticket.

How can I add a new boolean property to a Golang struct and set the default value to true?

I have a user struct that corresponds to an entity. How can I add a new property active and set the default value to true?
Can I also set the value of that property to true for all existing entities by some easy method?
type User struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
Bonus questions: I don't quite understand the syntax in the struct. What do the three columns represent? What do the JSON strings have ``around them?
//You can't change declared type.
type User struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
//Instead you construct a new one embedding existent
type ActiveUser struct {
User
Active bool
}
//you instantiate type literally
user := User{1, "John"}
//and you can provide constructor for your type
func MakeUserActive(u User) ActiveUser {
auser := ActiveUser{u, true}
return auser
}
activeuser := MakeUserActive(user)
You can see it works https://play.golang.org/p/UU7RAn5RVK
You have to set the default value as true at the moment when you are passing the struct type to a variable, but this means you need to extend that struct with a new Active field.
type User struct {
Id int64 `json:"id"`
Name string `json:"name"`
Active bool
}
user := User{1, "John", true}
json:"id" means that you are mapping the json decoded object field to the field id in your struct type. Practically you are deserialize the json string into object fields which later you can map to their specific field inside the struct.

Resources