type Users struct {
ID int64
Email string
Permissions string
}
type UserPermissions struct {
ID int64
Description json.RawMessage
}
The user json should be like this:
{
"status": 200,
"data": {
"id": 1,
"email": "hello#hello.com",
"permisions": [{
"id":"1",
"description":"Create permission"
},
{
"id":"3",
"description":"Edit permission"
}]
}
}
I have the following string in the Permissions of my User:
';1;3;5;7;' every number is the id related to the UserPermissions struct/table
How can I match the string with the user permission table using gorm?.
I'm using mysql
An answer to your question (if you are using PostgreSQL as your DB).
If you want to query for the Permissions that a given User record has, given its ID
perms := []Permission{}
err := db.Joins("JOIN users u ON id::string IN STRING_TO_ARRAY(TRIM(';' FROM u.permissions), ';')").
Where("u.id = ?", userID).
Find(&perms).
Error
if err != nil {
log.Fatal(err) // or something like that
}
As you can see, we are doing a funky join with functions that clean and split the permission field into individual string IDs. This is clunky, brittle and super slow and convoluted, and stems from the faulty relational design that you started with.
EDIT: Nevermind, for MySQL the equivalent solution is very much more complicated, so I suggest you instead use the advice below.
A better way
If you have control over the database design, the better way to do this would be
by not storing an array of IDs as a string, never a good thing in database design, but instead using a foreign key on the many side of the HasMany relationship*.
This would look like this as go structs:
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
Permissions []UserPermission `json:"permissions"`
}
type UserPermissions struct {
ID int64 `json:"id"`
UserID int64 `json:"-"`
Description json.RawMessage `json:"description"`
}
// now you can use gorm to query the permissions that are related to a user
user := User{}
err := db.Preload("Permissions").First(&user, userID).Error
if err != nil {
log.Fatal(err)
}
// now you can access user.Permissions[i].Description for example.
// or marshal to json
out, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(out))
*: I am assuming that the relationship between User and UserPermission is One-to-many. This may very well not be the case, and a Many-to-many relationship actually makes sense (One user can have many permissions, one permission can belong to many users). If this is the case, the concepts are the same and you should be able to modify this solution following the Gorm guide on many to many relationships.
Related
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)
I am using GORM in combination with Fiber. When querying all users with preloaded clause.Associations I get the following error:
can't preload field ###as### for entities.User
What does that mean? Without the preload of clause.Associations it works normally and but it does not show one-to-one associations.
func UsersGetAll(c *fiber.Ctx) error {
db := database.DBConn
users := []entities.User{}
records := db.Preload(clause.Associations).Find(&users)
if records.Error != nil {
log.Println(records.Error)
return c.Status(500).SendString(records.Error.Error())
}
return c.JSON(records.Value)
}
Well looks like a problem with gorm tags:
I will suppose the struct is something like this :
type Address struct {
ID int `gorm:"column:id;primaryKey"`
}
type User struct {
Name string `gorm:"column:name"`
Age string `gorm:"column:age"`
Address Address `gorm:"references:ID"`
}
And the code:
user:=User{}
if err:=db.Preload("Address").Find(&user).Error {
panic(err)
}
I think you also should look at this: Gorm relationship error: need to define a valid foreign key for relations or it need to implement the Valuer/Scanner interface
TL;DR
I'm looking for way to to get rid of the binding:"required" tag for specific endpoints (such as PUT) in Gin govalidate.
Issue
For the POST route, the password to create a new user is required.
For the PUT route, the password is not required, but still needs to be alphanumeric and a minimum of 8 characters.
Tried
Using different packages to see if I can change the binding when the endpoint is hit.
Created another struct called UserUpdate but feel this is really messy, particularly when the struct is large.
Using an embedded struct with such as below, however this couldn't be converted to the type User (I'm not sure if this is possible)
type User struct {
Password string `db:"password" json:"password,omitempty" "binding:min=8,alpha"
User
}
Example
// User Struct
type User struct {
Id int `db:"id" json:"id"`
FirstName string `db:"first_name" json:"first_name" binding:"required"`
LastName string `db:"last_name" json:"last_name" binding:"required"`
Password string `db:"password" json:"password,omitempty" "binding:required,min=8,alpha"`
}
// Update in Controller
func (c *UserController) Update(g *gin.Context) {
// Change from binding to nothing here!
var u domain.User
if err := g.ShouldBindJSON(&u); err != nil {
Respond(g, 400, "Validation failed", err)
return
}
}
I feel there must be something cleaner to tackle this problem!
Here is something similar, but doesn't quite answer the issue:
golang - elegant way to omit a json property from being serialized
I'm writing my first API, so bear with me. I am using Go, Postgres and GORM and a slew of other things I'm still picking up but I ran into an issue with GORM's AutoMigrate.
Initially my User struct looked like this:
type User struct {
gorm.Model
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
And when I ran db.AutoMigrate(&User{}) It auto-generated an id field in my User table (along with several date fields), which I wanted. What I am hung up on is figuring out how to reference these fields in my app. I have modified my User struct to now look like this:
type User struct {
gorm.Model
ID int `gorm:"primary_key" json:"id"`
Email string `gorm:"unique" json:"email"`
Password string `json:"password"`
}
But instead of linking the two id fields, when I access the stored user object as shown:
user := model.User{}
if err := db.First(&user, model.User{Email: email}).Error; err != nil {
respondError(w, http.StatusNotFound, err.Error())
return nil
}
there are now two distinct fields, the auto-generated and my own:
{
"ID": 2,
"CreatedAt": "2018-04-28T21:14:20.828547-04:00",
"UpdatedAt": "2018-04-28T21:14:20.828547-04:00",
"DeletedAt": null,
"id": 0,
"email": "joeynelson#gmail.com",
"password": <hash>
}
I realize the answer is likely right in front of my face, there must be a way to reference these auto-generated fields, right?
gorm.Model is a struct including some basic fields, which including fields ID, CreatedAt, UpdatedAt, DeletedAt.
Reference: http://gorm.io/docs/conventions.html#gorm-Model
Hypothetical, I run an API and when a user makes a GET request on the user resource, I will return relevant fields as a JSON
type User struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Secret string `json:"-,omitempty" bson:"secret,omitempty"`
}
As you can see, the Secret field in User has json:"-". This implies that in most operation that I would not like to return. In this case, a response would be
{
"id":1,
"Name": "John"
}
The field secret will not be returned as json:"-" omits the field.
Now, I am openning an admin only route where I would like to return the secret field. However, that would mean duplicating the User struct.
My current solution looks like this:
type adminUser struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
Is there a way to embed User into adminUser? Kind of like inheritance:
type adminUser struct {
User
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
The above currently does not work, as only the field secret will be returned in this case.
Note: In the actual code base, there are few dozens fields. As such, the cost of duplicating code is high.
The actual mongo query is below:
func getUser(w http.ResponseWriter, r *http.Request) {
....omitted code...
var user adminUser
err := common.GetDB(r).C("users").Find(
bson.M{"_id": userId},
).One(&user)
if err != nil {
return
}
common.ServeJSON(w, &user)
}
You should take a look at the bson package's inline flag
(that is documented under bson.Marshal).
It should allow you to do something like this:
type adminUser struct {
User `bson:",inline"`
Secret string `json:"secret,omitempty" bson:"secret,omitempty"`
}
However, now you'll notice that you get duplicate key errors
when you try to read from the database with this structure,
since both adminUser and User contain the key secret.
In your case I would remove the Secret field from User
and only have the one in adminUser.
Then whenever you need to write to the secret field,
make sure you use an adminUser.
Another alternative would be to declare an interface.
type SecureModel interface {
SecureMe()
}
Make sure your model implements it:
type User struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Username string `json:"username" bson:"username"`
Secret string `json:"secret,omitempty" bson:"secret"`
}
func (u *User) SecureMe() {
u.Secret = ""
}
And only call it depending on which route is called.
// I am being sent to a non-admin, secure me.
if _, ok := user.(SecureModel); ok {
user.(SecureModel).SecureMe()
}
// Marshall to JSON, etc.
...
Edit: The reason for using an interface here is for cases where you might send arbitrary models over the wire using a common method.