How to set associations deletedAt fields with soft delete? - go

How should I delete my associations in with soft delete?
I have the following structs
type PrivateGormModel struct {
ID uint `gorm:"primaryKey" json:"id,string"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt *time.Time `gorm:"index" json:"-"`
}
type Relation struct {
PrivateGormModel
OwnerID uint `json:"ownerID"`
OwnerType string `json:"ownerType"`
Addresses []Address `gorm:"polymorphic:Owner;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"addresses"`
Contacts []Contact `gorm:"polymorphic:Owner;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"contacts"`
People []Person `gorm:"polymorphic:Owner;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"people"`
BankAccounts []BankAccount `gorm:"polymorphic:Owner;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bankAccounts"`
}
type Company struct {
PrivateGormModel
Name string `json:"name"`
Relation Relation `gorm:"polymorphic:Owner;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"relation"`
}
I left out the adress, contact, person and bankaccount struct to keep this post short but they are simple structs with a OwnerID and OwnerType. And the following handler
func DeleteCompany(db *database.Database) fiber.Handler {
return func(c *fiber.Ctx) error {
id, err := IDFromParams(c)
if err != nil {
return c.JSON(responseKit.ParameterMissing())
}
toDelete := new(model.Company)
result := db.Preload("Relation.Addresses").
Preload("Relation.Contacts").
Preload("Relation.People").
Preload("Relation.BankAccounts").
Preload(clause.Associations).
First(toDelete, id)
fmt.Printf("\n%v", toDelete)
result = db.Select("Relation.Addresses").
Select("Relation.Contacts").
Select("Relation.People").
Select("Relation.BankAccounts").
Select("Relation").
Debug().
Delete(toDelete)
if result.Error != nil {
return c.JSON(responseKit.RecordDeleteError())
}
return c.JSON(responseKit.RecordDeleteSuccess())
}
}
Where the print outputs
{PrivateGormModel:{ID:5 CreatedAt:2021-01-15 11:24:03.672857 +0100 CET UpdatedAt:2021-01-15 11:24:03.672857 +0100 CET DeletedAt:<nil>} Name:Test Relation:{PrivateGormModel:{ID:5 CreatedAt:2021-01-15 11:24:03.738351 +0100 CET UpdatedAt:2021-01-15 11:24:03.738351 +0100 CET DeletedAt:<nil>} OwnerID:5 OwnerType:companies Addresses:[{PrivateGormModel:{ID:5 CreatedAt:2021-01-15 11:24:03.739322 +0100 CET UpdatedAt:2021-01-15 11:24:03.739322 +0100 CET DeletedAt:<nil>} OwnerID:5 OwnerType:relations Country:AA Zip:1111AB Number:1 Addition: Street:Test State:Test City:Test}] Contacts:[{PrivateGormModel:{ID:5 CreatedAt:2021-01-15 11:24:03.740319 +0100 CET UpdatedAt:2021-01-15 11:24:03.740319 +0100 CET
DeletedAt:<nil>} OwnerID:5 OwnerType:relations Tel:0612345678 Mail:test#test.com URL:}] People:[] BankAccounts:[{PrivateGormModel:{ID:5 CreatedAt:2021-01-15 11:24:03.740319 +0100 CET UpdatedAt:2021-01-15 11:24:03.740319 +0100 CET DeletedAt:<nil>} OwnerID:5 OwnerType:relations Bank:test BIC:test IBAN:test AccountHolder: Establishment:test}]}}
and the debug debugs the following
DELETE FROM "relations" WHERE "relations"."owner_type" = 'companies' AND "relations"."owner_id" = 5
DELETE FROM "companies" WHERE "companies"."id" = 5
So the relation is deleted. But it isn't doing anything for the hasMany relation. I read this
https://gorm.io/docs/associations.html#Delete-with-Select
And tried to do that since the constrains don't seem to do anything, but nothing seems to work and delete the adresses, contacts, people and bank accounts. How am I supposed to soft delete all the relationships of Company?

I've tested this with the latest version of Gorm, and Select and Delete only works for first-level associations.
So if you did
db.Select("Relation").Delete(toDelete)
You'd see that both Company and Relation get their DeletedAt set to the current timestamp.
To achieve soft-deletion of the second-level relations, you'd need to delete those in a separate call:
db.Select("Addresses", "Contacts", "People", "BankAccounts").Delete(toDelete.Relation)
// or more compactly
db.Select(clause.Associations).Delete(toDelete.Relation)
It may also be worthwhile asking yourself whether soft-deleting just the root of the model tree, and leaving everything else as-is isn't sufficient for your usecase.

Related

Gorm create and return value many2many

I want to create a data and then return the value, but the value is related to another table.
User response struct
type UserRespone struct {
ID int `json:"id,omitempty"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Address string `json:"address"`
Roles []roles.Roles `json:"roles" gorm:"many2many:users_roles;foreignKey:ID;joinForeignKey:UserID;references:ID;joinReferences:RolesID"`
}
Roles struct
type Roles struct {
ID int `json:"id" gorm:"<-:false;primaryKey"`
Name string
}
And this is a function to create a data, and I want to return the value [ ]roles.Roles
func (r *UserRepositoryImpl) AuthSignUp(ctx context.Context, req user.AuthSignUp) (user.UserRespone, error) {
createdUser := req.ToUser()
hashPassword, _ := bcrypt.GenerateFromPassword([]byte(createdUser.Password), bcrypt.DefaultCost)
createdUser.Password = string(hashPassword[:])
result := r.Db.
WithContext(ctx).
Create(&createdUser)
for _, rolesID := range req.RolesID {
userRole := new(user.UsersRoles)
userRole.UserID = createdUser.ID
userRole.RolesID = rolesID
result = r.Db.WithContext(ctx).Create(&userRole)
}
return createdUser.ToResponse(), result.Error
}
I want to return the value like this:
user.UserResponse{
ID: 4,
Email: "putri4#gmail.com",
FirstName: "putri",
LastName: "cantik",
Address: "bonang",
Roles: []roles.Roles{
{
ID: 1,
Name: "Admin",
},
{
ID: 2,
Name: "Consumer",
},
},
}
But when I create a data set, I only get values like :
user.UserRespones{
ID: 4,
Email: "putri4#gmail.com",
FirstName: "putri",
LastName: "cantik",
Address: "bonang",
Roles: []roles.Roles(nil),
}
As far as I can tell, you already have the user data and the role ids. I figure you just want to get the role names as well:
err := r.db.Find(&createdUser.Roles, req.RolesID)
// err handling
That said, your types and names are a bit unclear. UserRespone (note the typo) should probably named like DBUser - it isn't important whether it is used as response or something else, it represents the database entry for an user, right?
Additionally, I can only make assumptions about the type and fields of createdUser and UsersRoles, so I might have missed something.
I figure createdUser is of type UserRespone - but then I would expect that Roles is already complete (and you don't have to query anything at all). If not, then you should introduce a new type for that, e.g. RequestUser (as opposed to DBUser), which only contains Role ids but not Role names. Squeezing the data from the request, the db entry and the response data into the same type is confusing (and a tad too tightly coupled for my taste).

Preload with additional column

So I have a table of users where each user is associated with a main tool and tools in their backpacks. Each tool also have a "quality". But I have no idea of how to fetch this value so I appreciate any help on how to do this.
Databases
users
+----+------+--------------+
| id | name | main_tool_id |
+----+------+--------------+
| 1 | adam | 1 |
+----+------+--------------+
tools
+----+-------------+
| id | name |
+----+-------------+
| 1 | hammer |
| 2 | screwdriver |
+----+-------------+
user_tools
+----+---------+---------+---------+
| id | user_id | tool_id | quality |
+----+---------+---------+---------+
| 1 | 1 | 1 | LOW |
| 2 | 1 | 2 | HIGH |
+----+---------+---------+---------+
Models
type User struct {
Id int64 `json:"-"`
Name string `json:"name"`
MainToolId int64 `json:"-"`
MainTool Tool `json:"main_tool"`
ToolsInBackpack []Tool `json:"tools_in_backpack" gorm:"many2many:user_tools"`
}
type Tool struct {
Id int64 `json:"-"`
Name string `json:"name"`
Quality string `json:"quality"`
}
Code
var users []User
DB.Preload("MainTool").Preload("ToolsInBackpack").Find(&users) // <-- Modify this (I guess)
Real result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": ""
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": ""
},
{
"name": "screwdriver",
"quality": ""
},
]
}
Desired result
{
"name": "adam",
"main_tool": {
"name": "hammer",
"quality": "LOW"
},
"tools_in_backpack": [
{
"name": "hammer",
"quality": "LOW"
},
{
"name": "screwdriver",
"quality": "HIGH"
},
]
}
Thanks a lot for your help!
If you do not require MainTool to return an object.
maybe you uses it
type User struct {
ID uint `json:"-"`
Name string
MainTool int64
ToolsInBackpack string
Tools []*Tool
}
type Tool struct {
ID uint`json:"-"`
Name string
UserID uint
}
User{MainTool: 1, ToolsInBackpack: "[1,2]", Tools: []*Tool{&t1, &t2}}
if you don't wana to use string at ToolsInBackpack string you can't use datatypes.JSON at gorm.io/datatypes
for example
ToolsInBackpack datatypes.JSON
This isn't supported in Gorm so there won't be an easy way to just Preload and it's done.
Here are a few techniques you could look at:
Use a Custom Join Table Model that contains the fields that you need
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Quality string
}
err := db.SetupJoinTable(&User{}, "ToolsInBackpack", &UserTool{})
// handle error
(I left out the UserTools.ID field, Gorm doesn't need it, and you only need it if a user can have the same tool twice, in which case put it back on as part of the primaryKey. I don't know whether Gorm will be happy with it but try it out).
Then you can use this model to query the field as any other model:
userTools := []UserTool
err := db.Where("user_id = ?", user.ID).Find(&userTools).Error
// handle error
// now userTools[i].Quality is filled, you could use it to update the corresponding users[j].ToolsInBackpack[k].Quality
It's a pain because you need to match IDs as a post-processing step.
Use a Has-Many/Belongs-To hybrid relation to model the Many-To-Many Join Table:
Here the User Has Many UserTools and a Tool belongs to one or more UserTools. This effectively models the meaning of Many-to-Many (in ERD, a relationship like [User]>--<[Tool] can be decomposed into [User]--<[UserTool]>--[Tool]).
type User struct {
ID int64
Name string
MainToolId int64
MainTool Tool
ToolsInBackpack []UserTool
}
type UserTool struct {
UserID int64 `gorm:"primaryKey"`
ToolID int64 `gorm:"primaryKey"`
Tool Tool
Quality string
}
type Tool struct {
ID int64
Name string
}
Now you can Preload this association like this:
err := db.Model(&User{}).Preload("ToolsInBackpack.Tool").Find(&users).Error
// handle error
// now users[i].ToolsInBackpack[j].Quality and
// users[i].ToolsInBackpack[j].Tool.Name will be set correctly.
Only problem now is that you've got a weird shape in the model that you're then trying to marshal into JSON (most likely to be used in an API). My advice here is to split the DB model from the JSON API model, and have a mapping layer in your code. The DB model and the API Messages invariably diverge in important ways and trying to reuse one model for both soon leads to pain.

How to filter users by role in Gorm?

I found some issues understanding the documentation. I have REST API with Go and I tried to create an endpoint where I need to extract users only by their role. I tried different solutions, but can't achieve what I need. This is what I made and I'm not sure how to continue so I can take the users only by exact role. If someone can help me it will be appreciated, because I can't find more advanced stuff in the documentation.
The JSON response from this endpoint is:
[
{
"ID": 1,
"CreatedAt": "2020-12-09T14:40:55.171011+02:00",
"UpdatedAt": "2020-12-09T14:40:55.175537+02:00",
"DeletedAt": null,
"email": "h#go.com",
"password": "$2a$14$KN2wAOnfecAriBW0xeAJke.okEUlcpDHVeuk",
"bearer": "eyJhbGciOiJIUzI1NiIs2lhdCI6MTYwNjMwNTEzNn0.J2wBp8ecA9TebP6L73qZ1OZmo02DwQy9vTySt0fil4c",
"first_name": "H",
"last_name": "Pro",
"phone": "353456",
"salesforce_id": "sfsdddfsdf",
"webflow_id": "wfwfwfaawfw",
"Roles": null
},
{
"ID": 2,
"CreatedAt": "2020-12-09T14:40:55.171011+02:00",
"UpdatedAt": "2020-12-09T14:40:55.175537+02:00",
"DeletedAt": null,
"email": "s#go.com",
"password": "$2wAOnfecAriBW0xeAJke.okEUlcpDHVeuk",
"bearer": "eyJhbGiIs2lhdCI6MTYwNjMwNTEzNn0.J2wBp8ecA9TebP6L73qZ1OZmo02DwQy9vTy0fil4c",
"first_name": "S",
"last_name": "Test",
"phone": "3556",
"salesforce_id": "sfsdf",
"webflow_id": "wfwfwfw",
"Roles": null
}
]
User struct:
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []*Roles `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
Role struct:
type Roles struct {
gorm.Model
Name string `json:"name"`
Users []*User `gorm:"many2many:user_roles"`
}
The sql query I made and works for my case in Postgre:
SELECT * FROM users
JOIN user_roles ON users.id = user_roles.user_id
JOIN roles ON user_roles.roles_id = roles.id
WHERE user_roles.roles_id = 1
the User struct:
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []Roles `json:"roles" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
the struct of Roles
type Roles struct {
gorm.Model
Name string `json:"name"`
Users []*User `gorm:"many2many:user_roles"`
}
the endpoint:
func (h *Handler) GetAllEmployees(c *gin.Context) {
var users []models.User
//var roles []models.Roles //the comment here is because I get error message roles is defined but never used
var roleid int = 1
//you need to extract roleID from your query string or request body
tx := h.db.DB.
Preload("Roles").
Joins("INNER JOIN user_roles ON users.id = user_roles.user.id").
Joins("INNER JOIN roles ON user_roles.roles_id = roles.id").
Where("user_roles.roles_id = ?", roleid).
Find(&users)
if tx.Error != nil {
//handle error
}
c.JSON(200, users)
}
EDIT:
Based on all the answers below, following things need to happen here:
You need to rename Roles struct into Role
You need to load Roles for each object
Filter users by a specific role ID
First, you need to rename the Roles struct to Role, and change references to it:
Role struct
type Role struct {
gorm.Model
Name string `json:"name"`
Users []User `gorm:"many2many:user_roles"`
}
User struct
type User struct {
gorm.Model
Email string `json:"email"`
Password string `json:"password"`
Token string `json:"bearer"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Phone string `json:"phone"`
SalesforceID string `json:"salesforce_id"`
WebflowID string `json:"webflow_id"`
Roles []Role `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;many2many:user_roles;"`
}
Next, modify your code a bit. There is one join that is not necessary here:
func (h *Handler) GetAllEmployeesByRoleID(c *gin.Context) {
var users []models.User
var roleID int = 1
//you need to extract roleID from your query string or request body
tx := h.db.DB.
Preload("Roles").
Joins("INNER JOIN user_roles ur ON ur.user_id = users.id").
Where("ur.role_id = ?", roleID).
Find(&users)
if tx.Error != nil {
//handle error
c.JSON(500, tx.Error)
return
}
c.JSON(200, users)
}
So, you add Preload("Roles") to load all roles that a user can have, as I assumed that, even if you filter by roleID, you want to see all roles that a user can have.
You can use Joins and Where to construct a similar query you already used. A JOIN with the roles table isn't necessary since you have the needed info in the user_roles table.

Determine if POST data value matches struct field type

Using the gin framework I am trying to determine if POST'ed data does not match the struct field type and inform API user of their error.
type CreateApp struct {
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" valid:"string,omitempty"`
ApiVersion int64 `db:"api_version" json:"api_version" valid:"int,omitempty"`
}
...
func CreateApps(c *gin.Context) {
var json models.CreateApp
c.Bind(&json)
So when I POST
curl -H "Content-Type: application/json" -d '{"learn_more_image":"someimage.jpg","api_version":"somestring"}' "http://127.0.0.1:8080/v1.0/apps"
I would like to determine whether the POST'ed data for field 'api_version' (passed as string) does not match the struct field it is being binded to (int). If the data doesnt match I'd like to send a message back to the user. Its for this reason I was hoping I could loop through the gin contexts data and check it.
The gin function 'c.Bind()' seems to omit invalid data and all subsequent data fields with it.
Gin has a built-in validation engine: https://github.com/bluesuncorp/validator/blob/v5/baked_in.go
but you can use your own or disable it completely.
The validator does not validate the wire data (json string), instead it validates the binded struct:
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" binding:"required"`
ApiVersion int64 `db:"api_version" json:"api_version" binding:"required,min=1"`
Notice this: binding:"required,min=1"
Then:
err := c.Bind(&json)
or use a middleware and read c.Errors.
UPDATED:
Three workarounds:
Validate the json string your own (it can not be done with enconding/json)
Validate if integer is > 0 binding:"min=1"
Use a map[string]interface{} instead of a Struct, then validate the type.
func endpoint(c *gin.Context) {
var json map[string]interface{}
c.Bind(&json)
struct, ok := validateCreateApp(json)
if ok { /** DO SOMETHING */ }
}
func validateCreateApp(json map[string]interface{}) (CreateApp, bool) {
learn_more_image, ok := json["learn_more_image"].(string)
if !ok {
return CreateApp{}, false
}
api_version, ok = json["api_version"].(int)
if !ok {
return CreateApp{}, false
}
return CreateApp{
learn_more_image, api_version,
}
}

How to iterate through all the fields of an object

I have an object that has about 23 columns. Is there a way to iterate through each column automatically? Rather than specifically selecting each column using .get("COLUMN_NAME") ?
Thanks guys.
That's say a Class A -- with fields' id, createdAt, updatedAt, a, b, c and obj is an instance of A.
obj.attributes is an object which hold a, b, c and id, createdAt, updateAt are properties of obj.
The following is an example to show all fields' name except special field (id, createdAt, updatedAt) in web console.
Object.keys(obj.attributes).forEach(function(fieldName) {
console.log(fieldName);
});
To be more simple :
object.get('COLUMN_NAME') is equivalent to object.attributes.COLUMN_NAME
So if you do a console.log(object.attributes) you will have a JS Object as example :
{
"cheatMode":true
"createdAt":Tue Oct 30 2018 10:57:08 GMT+0100 (heure normale d’Europe centrale) {} (this is a JS Date object)
"playerName":"Sean Plott"
"score":1337
"updatedAt":Tue Oct 30 2018 12:18:18 GMT+0100 (heure normale d’Europe centrale) {} (this is a JS Date object)
}
With all attributes and their values.
That's all.
Full example code of ParseServer Query
const GameScore = Parse.Object.extend("GameScore");
const mainQuery = new Parse.Query(GameScore);
mainQuery.equalTo("cheatMode", true);
mainQuery.find().then(async (response) => {
response.map(function(object){
console.log(object.attributes)
// Will log for example :
// {
// "cheatMode":true
// "createdAt":Tue Oct 30 2018 10:57:08 GMT+0100 (heure normale d’Europe centrale) {} (this is a JS Date object)
// "playerName":"Sean Plott"
// "score":1337
// "updatedAt":Tue Oct 30 2018 12:18:18 GMT+0100 (heure normale d’Europe centrale) {} (this is a JS Date object)
// }
})
});

Resources