When you develop a RBAC system, a user may have many roles, a role maybe attached to many users. This is a typical many to many scenario.
In this case you will need three tables. A user table , a role table , and a pivot table user_has_roles logs users’ roles. In pivot table, user_has_roles.user_id is user table id ,user_has_roles.role_id is role table id. user_has_roles table may have some extra info about the role status suspended,revoked,expired time,granted by who etc.
type User struct { *goeloquent.EloquentModel Id int64 `goelo:"column:id;primaryKey"` UserName sql.NullString `goelo:"column:username"` Age int `goelo:"column:age"` Balance int `goelo:"column:balance"` Email sql.NullString `goelo:"column:email"` Roles []Role `goelo:"BelongsToMany:RolesRelation"` } func (u *User) TableName() string { return "users" } func (u *User) RolesRelation() *goeloquent.RelationBuilder { return u.BelongsToMany(u, &Role{}, "user_roles", "user_id", "role_id", "id", "id") }
BelongsToMany takes 6 parameter,first one is a pointer of current model(user),second is a pointer of related model(role), third one is pivotTable , 4th is pivotTable.selfKey(user_id) , 5th is pivotTable.relatedKey(role_id) ,last one is related model field correspond to 4th parameter.
Usage Example
Use With when retrive
var u UserT var us []UserT DB.Model(&UserT{}).With("Roles").Find(&u,4) //{select * from `users` where `id` in (?) limit 1 [4] {1} 61.560446ms} //{select `role`.*, `user_has_roles`.`user_id` as `goelo_pivot_user_id`, `user_has_roles`.`role_id` as `goelo_pivot_role_id`, `user_has_roles`.`status` as `goelo_pivot_status`, `user_has_roles`.`user_id` as `goelo_pivot_user_id`, `user_has_roles`.`granted_by` as `goelo_pivot_granted_by` from `role` inner join `user_has_roles` on `user_has_roles`.`role_id` = `role`.`rid` where `user_has_roles`.`user_id` in (?) and `user_has_roles`.`status` = ? [1 2] {0} 81.765708ms}
DB.Model(&UserT{}).With("Roles").Where("id", "<", 10).Get(&us) //{select * from `users` where `id` < ? [10] {3} 60.056161ms} //{select `role`.*, `user_has_roles`.`user_id` as `goelo_pivot_user_id`, `user_has_roles`.`role_id` as `goelo_pivot_role_id` from `role` inner join `user_has_roles` on `user_has_roles`.`role_id` = `role`.`rid` where `user_has_roles`.`user_id` in (?,?,?,?) [1 2 1 2] {0} 81.8815ms}
Directly Call Relation Method
var uu UserT var roles []Role DB.Model(&uu).Find(&uu,6) //{select * from `users` where `id` in (?) limit 1 [6] {1} 60.646603ms}
uu.RolesRelation().Get(&roles) //{select `roles`.*,`user_roles`.`user_id` as `goelo_pivot_user_id`,`user_roles`.`role_id` as `goelo_pivot_role_id` from `roles` inner join user_roles on `user_roles`.`role_id` = `roles`.`id` where `user_id` = ? [6] {2} 59.866877ms}
Many To Many (Inverse)
type Role struct { *goeloquent.EloquentModel Id int64 `goelo:"column:id;primaryKey"` Name string `goelo:"column:name"` DisplayName string `goelo:"column:display_name"` Users []User `goelo:"BelongsToMany:UsersRelation"` }
DB.Model(&Role{}).With("Users").Get(&rs) {select * from `role` [] {1} 81.365833ms} {select `user`.*, `user_has_roles`.`role_id` as `goelo_pivot_role_id`, `user_has_roles`.`user_id` as `goelo_pivot_user_id` from `user` inner join `user_has_roles` on `user_has_roles`.`user_id` = `user`.`id` where `user_has_roles`.`role_id` in (?) [1] {0} 81.665125ms}
Directly Call Relation Method
var rr models.Role var users []models.User DB.Model(&rr).Find(&rr,10) //{select * from `roles` where `id` in (?) limit 1 [10] {1} 62.39588ms} rr.Load("Users") //{select `users`.*,`user_roles`.`role_id` as `goelo_pivot_role_id`,`user_roles`.`user_id` as `goelo_pivot_user_id` from `users` inner join user_roles on `user_roles`.`user_id` = `users`.`id` where `user_roles`.`role_id` in (?) [10] {2} 60.394408ms}
rr.UsersRelation().Get(&users) //{select `users`.*,`user_roles`.`role_id` as `goelo_pivot_role_id`,`user_roles`.`user_id` as `goelo_pivot_user_id` from `users` inner join user_roles on `user_roles`.`user_id` = `users`.`id` where `role_id` = ? [10] {2} 60.804572ms}
Working with intermediate table
Like we said before in this chapter,
user_has_roles table may have some extra info about the role status suspended,revoked,expired time,granted by who etc.
Sometimes we want filter out roles granted by a certtain user , and with its expired time You can use WherePivot to filter intermediate table rows and WithPivot to select intermediate column
var manager UserT DB.Model(&UserT{}).With("Roles").Where("id", "<", 10).WherePivot("granted_by",manager.Id).WithPivot("ended_at","status").Get(&us) //{select `role`.*, `user_has_roles`.`user_id` as `goelo_pivot_user_id`, `user_has_roles`.`role_id` as `goelo_pivot_role_id`, `user_has_roles`.`ended_at` as `goelo_pivot_ended_at`, `user_has_roles`.`status` as `goelo_pivot_status` from `role` inner join `user_has_roles` on `user_has_roles`.`role_id` = `role`.`rid` where `user_has_roles`.`user_id` in (?,?,?,?,?,?,?) and `user_has_roles`.`granted_by` = ? [1 2 1 2 3 4 5 1] {0} 81.563375ms} //currently all intermediate table column are mapped as string , you need to convent it by yourself
for _, role := range user.Roles { fmt.Println(role.Pivot["status"].String) }