Ginを用いたAPIでGormで単方向リストを用いたい際に、要素の追加、ソート済みのリスト取得の実装を行ったのでメモとして残してみました。
ディレクトリ構成
go-app/svc/
├── components
│ ├── models.go
│ ├── repository.go
│ └── urls.go
└── services
├── models.go
├── repository.go
└── urls.go
モデルの条件
Serviceが一対多の関係で単方向リストとしてComponentを持つ
仕様
以下2つの実装を行いました。
- service_idから単方向リストの条件でソートされたcomponentsを取得
- service_id, component_idから対象のserviceの対象のcomponentの次の要素としてcomponentを追加
( component_id = 0 のとき、先頭に追加する)
Model
type Component struct {
gorm.Model
ServiceID uint
Title string
NextID uint
}
type Service struct {
gorm.Model
Title string
Components []components.Component `gorm:"foreignKey:ServiceID"`
}
サンプルとして、Service, ComponentにそれぞれTitleというカラムを用意し、
Componentにサービス、次の要素と関連付けるためのServiceID, NextIDを用意しました。
Url
package services
import (
"net/http"
"github.com/gin-gonic/gin"
"go-app/svc/components"
)
func Urls(router *gin.Engine) *gin.Engine {
router.GET("/services/get/:id", func(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, err)
} else {
c.JSON(200, get(id))
}
})
router.POST("/services/:service_id/append/component/:comp_id", func(c *gin.Context) {
// comp_idをもつcomponentの右側に追加する
// comp_id == 0の場合先頭に追加する
comp_id, _ := strconv.Atoi(c.Param("comp_id"))
service_id, _ := strconv.Atoi(c.Param("service_id"))
service := get(service_id)
var adding_comp components.Component
c.ShouldBindJSON(&adding_comp)
adding_comp.ServiceID = service.ID
if len(service.Components) == 0 {
// 初めて追加する場合
adding_comp.NextID = 0
components.Create(adding_comp)
} else {
if comp_id == 0 {
// 先頭に追加する場合
head_comp := service.Components[0]
adding_comp.NextID = head_comp.ID
components.Update(head_comp)
components.Create(adding_comp)
} else {
// 途中に追加or末端に追加する場合
target_comp := components.Get(comp_id)
adding_comp.NextID = target_comp.ID
adding_comp = components.Create(adding_comp)
target_comp.NextID = adding_comp.NextID
components.Update(target_comp)
}
}
c.JSON(202, adding_comp)
})
return router
}
先にservice_idからserviceを取得する関数と、service_idと右側に追加する対象となるcomponent_idからcomponentの追加、単方向リストとしての処理を実装しました。
serviceを取得する関数についての説明は省略します。
componentの挿入の関数の実装についてがかなり長くなってしまったのですが、以下のような処理を行っています。
- 対象のサービスがcomponentを一つも持っていない場合(新規追加)
- componentをNextID = 0 で保存
- 対象のサービスがcomponentを一つ以上持ち、comp_idのパラメータが0の場合(先頭に追加)
- serviceの中で先頭のcomponentを取得(head_comp)
- 先頭のcomponent(head_comp)のidを追加するcomponentのNextIDにセット
- 対象のサービスがcomponentを一つ以上持ち、comp_idのパラメータが0でない場合(挿入)
- comp_idから挿入場所のcomponentを取得(target_comp)
- 追加するcomponentのNextIDに挿入場所のcomponent(target_comp)のNextIDをセット
- 挿入場所のcomponent(target_comp)のNextIDに追加するcomponentのidをセット
Repository
package components
import (
"go-app/db_conf"
)
func Get(id int) Component {
db := db_conf.DBConnect()
var component Component
db.First(&component, "id=?", id)
db.Close()
return component
}
func GetPrev(next_id uint) Component {
db := db_conf.DBConnect()
var component Component
db.First(&component, "next_id=?", next_id)
db.Close()
return component
}
func GetTail(service_id int) Component {
db := db_conf.DBConnect()
var component Component
db.First(&component, "service_id=? AND next_id=?", service_id, 0)
db.Close()
return component
}
package services
import (
"go-app/svc/components"
"log"
)
func get(id int) Service {
db := db_conf.DBConnect()
var service Service
db.First(&service, "id= ?", id)
// componentのリストをorderdComponentsとして定義
var orderedComponents []components.Component
// 対象のserviceに関連付けられたcomponentの末端要素を配列に追加
orderedComponents = append(orderedComponents, components.GetTail(int(service.ID)))
log.Println("tail:", orderedComponents)
if orderedComponents[0].ID != 0 {
for i := 0; i < 100; i++ {
// 現在の先頭の要素から一つ前のcomponentを取得
now_component := components.GetPrev(orderedComponents[len(orderedComponents)-1].ID)
// 一つ前のcomponentが取得できなかったら終了
if now_component.ID == 0 {
break
}
// 取得した一つ前のcomponentを末端に追加
// この時点では逆順でsliceに追加されていく
orderedComponents = append(orderedComponents, now_component)
}
// ordeorderedComponentsを逆順に並び替える
for i := 0; i < len(orderedComponents)/2; i++ {
orderedComponents[i], orderedComponents[len(orderedComponents)-i-1] =
orderedComponents[len(orderedComponents)-i-1], orderedComponents[i]
}
service.Components = orderedComponents
}
db.Close()
return service
}
Goどころかコンパイル言語を触って1ヶ月に満たないので、甘いところが多いと思いますが、参考になれば幸いです。
指摘等あればお願いします。