LoginSignup
0
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-15

やること

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

DBのbool値をトグルするAPI

models

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

models/bool_toggler.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部分を実装します。

APIの実装

web/api/toggle_bool_toggler.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

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

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

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

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

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設計ですが認証機能がないため、

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

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

web/api/toggle_favorite_bool_toggler.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

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

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

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のテーブルも確認すると意図した挙動を確認できました。

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

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フレームワークのいいところですね。
結果はこんな構造体を返します。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0