値を二つの方法でトグルするAPIの実装(go) 

## やること

GoのwebフレームワークEchoになれるために練習するのが目的です。値をトグルするとは言っても、データベースのbool値をtrue<=>falseにする場合と、データベースのレコードをInsertしたりDeleteする場合(中間テーブルを使ってお気に入り機能を作るとか)があると思う。今回は、BoolTogglerモデルとして前者の実装をした後、UserモデルにBoolTogglerに対するのお気に入り機能をつけて後者を実装して行きます。(シュールで実用性に欠けるアプリケーションですが練習なので気にしないことにします...)
[GitHubにてコード全体を乗せています。](https://github.com/ngplus6655/example_echo_toggler_api)



## DBのbool値をトグルするAPI

### models

この記事の本題1です。モデルのbool型の列の値のTrue<=>Falseをトグルします。
こんなモデルを定義してマイグレーションしました。

models/bool_toggler.go

```go
package models

import (
    "github.com/jinzhu/gorm"
)

type BoolToggler struct {
	// type gorm.Model struct {
			// ID        uint           `gorm:"primaryKey"`
			// CreatedAt time.Time
			// UpdatedAt time.Time
			// DeletedAt gorm.DeletedAt `gorm:"index"`
	// }
  gorm.Model
  Toggler bool `json:"toggler"`
}
```

Togglerという単純なbool値をもつ構造体です。api部分を実装します。



### ＡPIの実装

web/api/toggle_bool_toggler.go

```go
package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"strconv"
	"hello/models"
	"hello/middlewares"
)

func ToggleBoolToggler() echo.HandlerFunc {
	return func(c echo.Context) error {
        // DB接続ミドルウェア
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		// パスパラメータはstringなのでuintに変換する
		intId, _ := strconv.Atoi(c.Param("id"))
		uintId := uint(intId)

		boolToggler := models.BoolToggler{}
		if dbs.DB.First(&boolToggler, uintId).RecordNotFound() {
			// idの指定が誤っている場合はステータスコード404を返す。
			return c.JSON(fasthttp.StatusNotFound, "指定したidのboolTogglerが見つかりませんでした。")
		} else {
			// bool値を反転させて保存する
			boolToggler.Toggler = !boolToggler.Toggler
			dbs.DB.Save(&boolToggler)
			return c.JSON(fasthttp.StatusOK, boolToggler)
		}
	}
}
```



### 現在設定されているTogglerを返すエンドポイント

web/api/get_bool_toggler.go

```go
package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"hello/models"
	"hello/middlewares"
	"strconv"
)

func GetBoolToggler() echo.HandlerFunc {
	return func(c echo.Context) error {
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		intId, _ := strconv.Atoi(c.Param("id"))
		uintId := uint(intId)

		boolToggler := models.BoolToggler{}
		if dbs.DB.First(&boolToggler, uintId).RecordNotFound() {
			return c.JSON(fasthttp.StatusNotFound, "指定したidのboolTogglerが見つかりませんでした。")
		} else {
			return c.JSON(fasthttp.StatusOK, boolToggler.Toggler)
		}
	}
}
```

基本的にはさっきと同じです。データを更新する代わりにブール値だけを取り出してレスポンスに入れています。

routes/api.go

```go
func Init(e *echo.Echo) {
	g := e.Group("/api")
    {
        g.PUT("/bool_toggler/:id/toggle", api.ToggleBoolToggler())
        g.GET("/bool_toggler/:id", api.GetBoolToggler()) 
    }
}
```

ここまで動作ををテストしてみます。

```bush
curl http://localhost:8080/api/bool_toggler/1
>> false

curl -XPUT http://localhost:8080/api/bool_toggler/1/toggle
>> {"ID":1,"CreatedAt":"2020-10-05T14:54:27Z","UpdatedAt":"2020-10-07T10:49:12.1435735Z","DeletedAt":null,"toggler":true}

curl http://localhost:8080/api/bool_toggler/1
>> true

// 未登録データ
curl http://localhost:8080/api/bool_toggler/3
>> "指定したidのboolTogglerが見つかりませんでした。
```

といった感じでうまく動作していることが確認できました。



## お気に入り状態をトグルするAPI

### models

中間テーブルで関連を表現するデータにおいて値をDeleteしたり、insertしたりするAPIを実装していきます。

単純なUserモデルを作るところから始めてきます。
中間テーブルによってリレーショナルな多対多なデータを単純なアプリケーションとして実装するのが目的なので、認証機能は作りません。とにかくシンプルにタイムスタンプと名前だけのモデルです。同様にマイグレーションします。

GORMで多対多のデータベースを作るには、https://gorm.io/ja_JP/docs/many_to_many.html

models/user.go

```go
package models

import (
  "github.com/jinzhu/gorm"
)

type User struct {
	gorm.Model
	name string `json:"name"`
	// Favoritesという名前でbool_togglerを格納します
	Favorites []*BoolToggler `gorm:"many2many:user_favorite_togglers;"`
}
```



### APIの実装

先にURI設計ですが認証機能がないため、

 ```url
/api/favorite/users/:user_id/bool_togglers/:toggler_id
 ```

というURIで複数のパスパラメータからそれぞれの実体を参照することが必要だということがわかります。以下API部分ですがかなり複雑になってきたのでコメントを多めに残しました

web/api/toggle_favorite_bool_toggler.go

```go
package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"strconv"
	"hello/models"
	"hello/middlewares"
)

func ToggleFavoriteToggler() echo.HandlerFunc {
	return func(c echo.Context) error {

		// response用のJSONを作る構造体
		type Response struct {
			UserId         uint
			BoolTogglerId  uint
			Favorite       bool
		}

		// DB接続
		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		
		// パスパラメータをuintに変換する
		intUserId, _ := strconv.Atoi(c.Param("user_id"))
		uintUserId := uint(intUserId)
		intTogglerId, _ := strconv.Atoi(c.Param("toggler_id"))
		uintTogglerId := uint(intTogglerId)

		// Response構造体をインスタンス化
		var resJSON Response
		resJSON.UserId = uintUserId
		resJSON.BoolTogglerId = uintTogglerId

		// AppendするときにIDを指定したboolTogglerを渡す
		var boolToggler models.BoolToggler
		boolToggler.ID = uintTogglerId
		
		// Preloadでuserのリレーションを有効化してselect
		user := &models.User{}
		dbs.DB.Preload("Favorites", "bool_toggler_id = ?", uintTogglerId).
		Find(&user, uintUserId)

		// まだお気に入りされていなかった場合は、新しいレコードを追加
		if len(user.Favorites) < 1 {
			dbs.DB.Model(&user).Association("Favorites").Append(&boolToggler)
			resJSON.Favorite = true

		// お気に入り済みだった場合は、既存のレコードを削除
		} else {
			dbs.DB.Model(&user).Association("Favorites").Delete(&boolToggler)
			resJSON.Favorite = false
		}
		return c.JSON(fasthttp.StatusOK, resJSON)
	}
}

```

routes/api.go

```go
g.POST("/favorite/users/:user_id/bool_togglers/:toggler_id", api.ToggleFavoriteToggler()) //追記
```

一応curlしてみるとちゃんと値をトグルしています。

```bush
curl -XPOST http://localhost:8080/api/favorite/users/1/bool_togglers/1
{"UserId":1,"BoolTogglerId":1,"Favorite":false}
/go/src/app # curl -XPOST http://localhost:8080/api/favorite/users/1/bool_togglers/1
{"UserId":1,"BoolTogglerId":1,"Favorite":true}
```

さらに、mysqlのテーブルも確認すると意図した挙動を確認できました。

```bush
mysql> select * from user_favorite_togglers;
+---------+-----------------+
| user_id | bool_toggler_id |
+---------+-----------------+
|       1 |               1 |
+---------+-----------------+
1 row in set (0.00 sec)

// アクセス後
mysql> select * from user_favorite_togglers;
Empty set (0.00 sec)
```



### user情報を返すエンドポイント

web/api/show_user.go

```go
package api

import (
	"github.com/labstack/echo"
	"github.com/valyala/fasthttp"
	"hello/models"
	"hello/middlewares"
	"strconv"
)

func ShowUserInfo() echo.HandlerFunc {
	return func(c echo.Context) error {

		type ResToggler struct {
			BoolTogglerId uint
			Toggler       bool
		}

		type Response struct {
			UserId uint
			Name   string
			Favorites []ResToggler
		}

		dbs := c.Get("dbs").(*middlewares.DatabaseClient)
		// パスパラメータをuintに変換する
		intUserId, _ := strconv.Atoi(c.Param("id"))
		uintUserId := uint(intUserId)
		
		// Preloadでuserのリレーションを有効化してselect
		user := models.User{}
		dbs.DB.Preload("Favorites").Find(&user, uintUserId)

		var resJSON Response
		resJSON.UserId = user.ID
		resJSON.Name   = user.Name
		for _, v := range user.Favorites {
			toggler := ResToggler{
				BoolTogglerId: v.ID,
				Toggler: v.Toggler,
			}
			resJSON.Favorites = append(resJSON.Favorites, toggler)
		}
		return c.JSON(fasthttp.StatusOK, resJSON)
	}
}
```

Response用の構造体を定義しておくと、そのままJSONとして渡せるのがEchoフレームワークのいいところですね。
結果はこんな構造体を返します。

```bush 
curl http://localhost:8080/api/user/1
{"UserId":1,"Name":"test_user01","Favorites":[{"BoolTogglerId":1,"Toggler":false}]}
```