Extra Column in self-referencing model - go

I just started to use Gorm and I am encountering troubles to define a model.
I have succeeded in creating a join table by following the doc example for self-referencing model:
type User struct {
gorm.Model
Friends []*User `gorm:"many2many:friendships;association_jointable_foreignkey:friend_id"`
}
The problem is I want an extra column in my join table to know whether a friend is accepted or not. I have looked for an answer through the internet but the only answer that I found is this :
https://github.com/jinzhu/gorm/issues/719#issuecomment-168485989
but I don't understand how I can adapt it in my case.
If anyone out there has any examples and/or solutions, I will be grateful.
Thanks again !

This answer is 4 years late but I had the same issue today so hope this helps anyone.
To add an extra column to a join table created by a self-referencing many-to-many relationship in GORM, you need to create a custom join table. In this post's case, the models would look like this:
type User struct {
gorm.Model
Friends []*User `gorm:"many2many:friends"`
}
// custom join table
type Friend struct {
UserId int `gorm:"primary_key"`
FriendId int `gorm:"primary_key"`
Accepted bool
}
Then, you use the SetupJoinTable method BEFORE you use the AutoMigrate method. This wasn't working for me until I called the join method prior to the migrate method:
err := db.SetupJoinTable(&User{}, "Friends", &Friendship{})
err = db.AutoMigrate(&User{})
This should create the join table with the extra column.
Hope this helped!

Related

How to permanently delete associations in GORM

I want to know how to permanently delete associations in GORM. I tried all examples shown in the documentation but I cannot get associations to become permanently deleted. For example, I am confused by GORM's documentation on deleting and clearing associations, which explicitly says: won't delete those objects from DB. (I don't understand what it means to delete objects without deleting them from the database.)
I have similar structs:
type User struct {
gorm.Model
City string `sql:"type:varchar(255);not null"`
Cards []Card `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
}
type Card struct {
ID uint `gorm:"primary_key"`
UserID uint `gorm:"column:user_id"`
}
I want to execute the following SQL query in GORM form:
DELETE c
FROM cards c
JOIN users u ON c.user_id = u.id
WHERE u.name = `Madrid`
gorm.Model is including a DeletedAt field. So on deletion, this will be set to the current date, the record won't be removed from the database, but will not be findable with normal query methods. They call that "soft delete".
In order to delete the record permanently you have to use Unscoped, like:
db.Unscoped().Delete(&order)
Source: https://gorm.io/docs/delete.html

Querying Many to Many with Conditions

I have a gorm many2many model like this:
type User struct {
gorm.Model
Username string
LikedBooks []Books `gorm:"many2many:user_liked_books;"`
}
type Book struct {
gorm.Model
Name string
Likes []User `gorm:"many2many:user_liked_books;"`
}
Where a User can like many Books and a Book can have many Users that like it.
I now want to query for Books that have been liked, and return the top 50 liked books.
How can I achieve that using gorm? I dont understand how to query with conditions on book.liked = true, sorted by liked count, limited by 50.
I couldnt find an example like that in the docs or on stackoverflow.
This could be done in the same way you would construct a SQL query for the request you described, just using the gorm functions. It could look something like this:
var books []Book
tx := db.Table("books").
Joins("INNER JOIN user_liked_books ulb ON ulb.book_id = books.id").
Select("books.id, books.name, count(ulb.user_id) as likes_count").
Group("books.id, books.name").
Order("likes_count desc").
Limit(50).
Find(&books)
If you would also want to load the Likes field, try adding .Preload("Likes") to the construct above.

How to search on a many-to-many field

Let's assume I have the below structs
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;"`
}
type Skill struct {
Name string `json:"name" gorm:"primary_key"`
}
To query all the jobs I do:
jobs := &[]Job{}
gorm.DB.Preload("Skills").Find(&jobs)
How do I search for a Job that contains a certain skill? I have tried the below but it says the column does not exist.
s := "golang"
jobs := &[]Job{}
gorm.DB.Preload("Skills").Where("skill = ?", s).Find(&jobs)
I can see my issues, = doesn't seem to be the correct operator as it needs to search in a list. And it also isn't loading the join table as I assumed it would
Debug output
pq: column "skill" does not exist
SELECT * FROM "jobs" WHERE skill = 'golang'
The Preload method and the Associations feature help you load your fields by constructing basic SQL queries based on the relationships you have created. Queries like loading all skills for a specific job (or jobs). But it doesn't go any more complex than that.
Rather, think of go-gorm as a way to construct your SQL queries and load the data into your models.
Having that in mind, one solution would be to use Joins to include the skill table.
gorm.DB.Preload("Skills")
.Joins("INNER JOIN job_skill js ON js.job_id = jobs.id").
.Joins("INNER JOIN skill s ON s.id = js.skill_id").
.Where("s.name = ?", s).Find(&jobs)

"Creation At" time in GORM Customise Join table

I am trying to customize many2many table join. I have two tables from which I want to have taken the ids and want another field, which will tell me when the entry in the join table was made. The ids are coming fine, but the "created_at" is not updating and shows "Null" instead of time.
// this is the table join struct which I want to make
type UserChallenges struct {
gorm.JoinTableHandler
CreatedAt time.Time
UserID int
ChallengeID int
}
//hook before create
func (UserChallenges) BeforeCreate(Db \*gorm.DB) error {
Db.SetJoinTableHandler(&User{}, "ChallengeId", &UserChallenges{})
return nil
}
This is not giving any error on the build. Please tell me what I am missing so that I can get the creation time field in this.
PS - The documentation of GORM on gorm.io is still showing SetupJoinTable method but it is deprecated in the newer version. There is a SetJoinTableHandler but there is no documentation available for it anywhere.
The thing to get about using a Join Table model is that if you want to access fields inside the model, you must query it explicitly.
That is using db.Model(&User{ID: 1}).Association("Challenges").Find(&challenges) or db.Preload("Challenges").Find(&users), etc. will just give you collections of the associated struct and in those there is no place in which to put the extra fields!
For that you would do:
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Find(&joins)
// now joins contains all the records in the join table pertaining to user.ID,
// you can access joins[i].CreatedAt for example.
If you wanted also to retrieve the Challenges with that, you could modify your join struct to integrate the BelongsTo relation that it has with Challenge and preload it:
type UserChallenges struct {
UserID int `gorm:"primaryKey"`
ChallengeID int `gorm:"primaryKey"`
Challenge Challenge
CreatedAt time.Time
}
joins := []UserChallenges{}
db.Where("user_id = ?", user.ID).Joins("Challenge").Find(&joins)
// now joins[i].Challenge is populated

Is there a way to add a custom column when we create a many2many association in gorm?

I want to know 2 things
I got the following structs from a similar question.
// models/school.go
type School struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null"`
Accreditations []Accreditation `gorm:"many2many:school_accreditation;"`
}
type Accreditation struct {
// "accreditation" table
ID int `gorm:"primary_key"`
Name string
Description string
}
So, by default this will create a school_accreditation table with 2 columns:
one will have School's ID
other will have Accreditation's ID
My questions:
What is the most efficient way to add another column to the school_accreditation table?
Let's say I want to have the Name field of Accreditation in the school_accreditation table.
2.1) How do I achieve this eg: school_accreditation will have school_id, accreditation_id, accreditation_name
For the 1st question,
It seems the only way of doing it by defining the SchoolAccreditation model & adding fields to it.
Also, it gives me more control over the relationship.
For the 2nd question, I am yet to find a way if there is any!
bost
For the 2nd,
You have to manually get the name from Accreditation & then add it to the column via the defined SchoolAccreditation model.

Resources