[Golang, Gorm]単方向リストの実装

Ginを用いたAPIでGormで単方向リストを用いたい際に、要素の追加、ソート済みのリスト取得の実装を行ったのでメモとして残してみました。
## ディレクトリ構成

go-app/svc/
├── components
│   ├── models.go
│   ├── repository.go
│   └── urls.go
└── services
    ├── models.go
    ├── repository.go
     └── urls.go

## モデルの条件
Serviceが一対多の関係で単方向リストとしてComponentを持つ
![Untitled.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1678850/80681311-a333-a076-6fb6-0cabf77a65da.jpeg)



## 仕様
以下2つの実装を行いました。

1. service_idから単方向リストの条件でソートされたcomponentsを取得
2. service_id, component_idから対象のserviceの対象のcomponentの次の要素としてcomponentを追加
( component_id = 0 のとき、先頭に追加する)

# Model

```go
type Component struct {
	gorm.Model
	ServiceID uint
	Title     string
	NextID    uint
}
```

```go
type Service struct {
	gorm.Model
	Title      string
	Components []components.Component `gorm:"foreignKey:ServiceID"`
}
```

サンプルとして、Service, ComponentにそれぞれTitleというカラムを用意し、
Componentにサービス、次の要素と関連付けるためのServiceID, NextIDを用意しました。
# Url

```go
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

```go
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
}
```

```go
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ヶ月に満たないので、甘いところが多いと思いますが、参考になれば幸いです。
指摘等あればお願いします。