Gorm create and return value many2many - go

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).

Related

How to use a struct field into another struct without referring to it as a key

I want to insert a struct field into another struct without having to use the struct name.
I know that I can do this:
type Person struct {
Name string
}
type User struct {
Person
Email, Password string
}
But It results in this struct:
user := User{Person: Person{Name: ""}, Email: "", Password: ""}
How can I do something like this:
type Person struct {
Name string
}
type User struct {
Name Person.Name // Here
Email, Password string
}
To use it like this
user := User{Name: "", Email: "", Password: ""}
Is it possible?
Simply put, with the current language implementation you can't.
When initialising a literal you need to be explicit (or, put another way: literal! [sic]). Since a User contains a Person, a literal User must contain a literal Person, as you illustrate:
u := User{
Person: Person{
Name: "Bob",
},
Email: "bob#bobspot.com",
Password: "you're kidding right?",
}
However, once you have a variable of type User, you can then leverage the anonymous field to set (or get) the Name of the anonymous Person with the User:
u := User{}
u.Name = "Bob"
u.Email = "bob#bobspot.com",
u.Password = "you're kidding right?",
Why Does Go Make Me Do All This Work?
Let us imagine that it was possible to initialise the inner Person in the way you are looking for:
u := User{ Name: "Bob" }
Now let us further imagine that we later modify the User struct and add its own Name field:
type User struct {
Person
Name string
Email string
Password string
}
And now you can obviously initialise the new Name field:
u := User{ Name: "Bob" }
Notice that this is identical to the previous code that initialised User.Person.Name but now it is initialising User.Name. Not good.
More Gotchas
There are further traps lurking with code like this.
First, the addition of a Name field in User already similarly "breaks" unqualified references to Name on User variables:
u.Name = "Bob" // used to set User.Person.Name, now sets User.Name
Also, with only an anonymous Person field, the User.Person.Name field is marshalled to JSON by default as a "Name" field:
{
"Name": "",
"Email": "",
"Password": ""
}
If a Name field is added, then this is the field that is marshalled as "Name" and the User.Person.Name field is not marshalled at all.
You might think you can add a json tag for the User.Person.Name, e.g.
type User struct {
Person `json:"PersonName"`
Name string
Email string
Password string
}
But now the Person is marshalled as an object with a Name field:
{
"PersonName": {
"Name": ""
},
"Name": "",
"Email": "",
"Password": ""
}
This also happens if you try to change the marshalled field name of the anonymous Person even if User does not have a Name field.
In short: using anonymous structs within structs as a way of "adding fields" is potentially problematic and fragile and should probably be avoided.

Pattern for writing graphql schema with gorm decorator?

I am writing graphql schema that need to work with gorm, my understanding is that graphql can't inherently use gorm decorator, so to make it work, I wrote a separate Output type:
Let's say I have this table ORM:
type Character struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"unique" json:"name"`
CliqueType string `json:"clique"`
IsHero bool `json:"hero"`
}
This is hardcoded and put into a model.go file. The gorm decorator works here as this is native go file.
In the schema.graphql I have this instead:
type CharacterOutput {
id: ID!
name: String!
isHero: Boolean!
cliqueType: CliqueType!
}
input CharacterInput {
name: String!
isHero: Boolean
cliqueType: CliqueType
}
As you can see, I have both a input and output graphql schema definition. This allow graphql to generate the required type without conflicting with gorm.
The downside of this is that I need to write a separate manual mapper between the 2 type, which kind of defeat the purpose of a ORM:
func mapChar2Output(character *model.Character) *model.CharacterOutput {
output := model.CharacterOutput{
ID: strconv.FormatUint(uint64(character.ID), 10),
Name: character.Name,
CliqueType: model.CliqueType(character.CliqueType),
IsHero: character.IsHero,
}
return &output
}
func mapInputToChar(CharacterInput *model.CharacterInput, Character *model.Character) {
Character.Name = CharacterInput.Name
Character.IsHero = *CharacterInput.IsHero
Character.CliqueType = CharacterInput.CliqueType.String()
}
My graphql resolver look like this, notice I call the mapper after getting the result from repo:
// CreateCharacter is the resolver for the createCharacter field.
func (r *mutationResolver) CreateCharacter(ctx context.Context, input model.CharacterInput) (*model.CharacterOutput, error) {
result, err := r.Resolver.CharacterRepository.CreateCharacter(&input)
output := mapChar2Output(result)
return output, err
}
My database CRUD function call the input mapper:
func (b *CharacterService) CreateCharacter(CharacterInput *model.CharacterInput) (*model.Character, error) {
character := &model.Character{}
mapInputToChar(CharacterInput, character)
err := b.Db.Create(&character).Error
return character, err
}
What is a better way of designing this schema so graphql can work with gorm without needing to write any extra mapper?

why create a new row when gorm update associations

I followed the document code same struct and migrate
here's my model
type User struct {
gorm.Model
AddressID uint ``
Address UserAddress `gorm:"references:ID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
}
type UserAddress struct {
gorm.Model
Address string `gorm:"type:varchar(50);notNull"`
}
document works fine when migrate and create data
db.AutoMigrate(&UserAddress{})
db.AutoMigrate(&User{})
but when I try update data use associations
user := &User{
ID: 1,
AddressID: 1,
Address: UserAddress{
Address: "new address",
},
}
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
document says:
If you want to update associations’s data, you should use the FullSaveAssociations mode:
then it still create a new data of address and update user.address_id
INSERT INTO `user_address` (`address`,`created_at`,`updated_at`,`deleted_at`) VALUES ('changed','2021-08-13 14:07:39.149','2021-08-13 14:07:39.149',NULL) ON DUPLICATE KEY UPDATE `address`=VALUES(`address`),`created_at`=VALUES(`created_at`),`updated_at`=VALUES(`updated_at`),`deleted_at`=VALUES(`deleted_at`)
where am I missing here or it just doesn't work at all?
You didn't give a unique id for Address. So it create new address record with autoincrement ID. And update the ID value associated with it.
user := &User{
ID: 1,
AddressID: 1,
Address: UserAddress{
ID: 1,
Address: "new address",
},
}

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.

graphql - querying multiple tables

I am new to graphql and I need to query multiple tables at once to display some data. I have a dashboard that shows information on a home where it comes from 5 tables: address, person, hostinfo, room, and image. I initially have the person_id to query address table which contains the person_id etc... Here's what a brief scratch up of a uml looks like:
entity Address {
AddressId BigInteger,
FK_PersonId BigInteger,
StreetNumber Integer,
...
}
entity HostInfo {
HostInfoId BigInteger,
FK_AddressId BigInt,
HomestayName String,
...
}
entity Room {
RoomId BigInteger,
FK_HostInfoId BigInteger,
RoomName String,
...
}
entity Image {
FK_AddressId BigInt,
FK_RoomID BigInt,
Name String,
....
}
entity Person {
PersonId,
FirstName String,
Age Integer required, //TODO remember to check age
}
My question is, how do I use graphql to grab all the data from these tables using just PersonId?
EDIT--------
I refactored the typedefiniton as follows:
export type Address = {
StreetNumber: number,
//..
Person:Person
}
export type HostInfo = {
HomestayName: string,
//..
Person:Person,
Room:[Room!],
Address:Address
}
export type Room = {
RoomName: string,
//..
RoomImage:[RoomImage!],
HostInfo:HostInfo
}
export type RoomImage = {
Name: string,
//..
Room:Room,
}
export type HostInfoImage = {
Name: string,
..
HostInfo:HostInfo
}
export type PersonImage = {
Name: string,
//..
Person:Person
}
export type Person = {
FirstName: string,
..
PersonImage:[PersonImage]
Address:Address
}
and perform the query as so:
query HostInfo($id: ID!) {
node(id: $id) {
... on Person {
firstName
address {
streetNumber
hostInfo {
HostName,
//...
Room {
RoomName,
[RoomImage],
//....
}
}
}
}
}
}
I would explicitly show the relationship both ways in my entities... I was expecting more of a sql(ly) way to do it.
Typically GraphQL would represent all of the links between objects explicitly in the schema: if in your database model an Address references a Person, then your Person GraphQL type would have a list of addresses. In the GraphQL schema language, you might have:
type Person implements Node {
id: ID!,
firstName: String,
age: Int!,
address: [Address!]!
}
If you knew the ID of a person, a typical query to retrieve much of the available data might hypothetically look like
query Person($id: ID!) {
node(id: $id) {
... on Person {
firstName
age
address {
streetNumber
hostInfo { ... }
}
}
}
}

Resources