golangでCRUDアプリを実装する

# 概要
golangのWebフレームワークである[gin](https://github.com/gin-gonic/gin)でCRUDアプリケーションを実装したので、これからgolangを勉強したいといった方の参考になるように簡単にまとめたいと思います。

# 作成したWebアプリケーション
自分が今まで読んできた本のタイトル、値段、合計冊数、合計金額を記録していけるWebアプリです。読んできた本の合計金額や合計冊数を数値化して記録することで、読書に対するモチベーションを高めたいという個人的な思いから作りました！
![golang-web1.gif](https://qiita-image-store.s3.amazonaws.com/0/343290/2d9b6a33-2322-d6cb-203d-ed2ba189591e.gif)

# ソースコード

```main.go
package main

import (
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"

	_ "github.com/mattn/go-sqlite3"
)

type Book struct {
	gorm.Model
	Title string
	Price int
}

type Result struct {
	Total int
}

// DB migration
func dbInit() {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbInit())")
	}
	defer db.Close()
	db.AutoMigrate(&Book{})
}

// DB Create
func dbInsert(title string, price int) {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbInsert())")
	}
	defer db.Close()
	db.Create(&Book{Title: title, Price: price})
}

// DB Update
func dbUpdate(id int, title string, price int) {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbUpdate())")
	}
	defer db.Close()
	var book Book
	db.First(&book, id)
	book.Title = title
	book.Price = price
	db.Save(&book)
}

// DB Delete
func dbDelete(id int) {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbDelete())")
	}
	defer db.Close()
	var book Book
	db.First(&book, id)
	db.Delete(&book)
}

// DB All Get
func dbGetAll() []Book {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbGetAll())")
	}
	defer db.Close()
	var books []Book
	db.Order("created_at desc").Find(&books)
	return books
}

// DB One Get
func dbGetOne(id int) Book {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbGetOne())")
	}
	defer db.Close()
	var book Book
	db.First(&book, id)
	return book
}

func dbGetNum() int {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbGetNum())")
	}
	defer db.Close()
	var num int
	db.Table("books").Count(&num)
	return num
}

func dbGetPrice() Result {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	if err != nil {
		panic("You can't open DB (dbGetPrice())")
	}
	defer db.Close()
	var result Result
	db.Table("books").Select("sum(price) as total").Scan(&result)
	return result
}

func main() {
	router := gin.Default()
	router.LoadHTMLGlob("views/*.html")

	dbInit()

	//Index
	router.GET("/", func(c *gin.Context) {
		books := dbGetAll()
		num := dbGetNum()
		sumPrice := dbGetPrice()
		c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})
	})

	//Create
	router.POST("/new", func(c *gin.Context) {
		title := c.PostForm("title")
		p := c.PostForm("price")
		price, err := strconv.Atoi(p)
		if err != nil {
			panic(err)
		}
		dbInsert(title, price)
		c.Redirect(302, "/")
	})

	//Edit
	router.GET("/edit/:id", func(c *gin.Context) {
		n := c.Param("id")
		id, err := strconv.Atoi(n)
		if err != nil {
			panic(err)
		}
		book := dbGetOne(id)
		c.HTML(200, "edit.html", gin.H{"book": book})
	})

	//Update
	router.POST("/update/:id", func(c *gin.Context) {
		n := c.Param("id")
		id, err := strconv.Atoi(n)
		if err != nil {
			panic(err)
		}
		title := c.PostForm("title")
		p := c.PostForm("price")
		price, errPrice := strconv.Atoi(p)
		if errPrice != nil {
			panic(errPrice)
		}
		dbUpdate(id, title, price)
		c.Redirect(302, "/")
	})

	//delete
	router.POST("/delete/:id", func(c *gin.Context) {
		n := c.Param("id")
		id, err := strconv.Atoi(n)
		if err != nil {
			panic(err)
		}
		dbDelete(id)
		c.Redirect(302, "/")
	})

	//delete_confirm
	router.GET("/delete_confirm/:id", func(c *gin.Context) {
		n := c.Param("id")
		id, err := strconv.Atoi(n)
		if err != nil {
			panic(err)
		}
		book := dbGetOne(id)
		c.HTML(200, "delete.html", gin.H{"book": book})
	})

	router.Run()
}
```

```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>一覧ページ</title>
  </head>
  <body>
    <header>
      <h1>Bookshelf</h1>
    </header>
    <div class="wrap">
      <div class="input">
        <form action="/new" method="post">
          <p>Title :<input type="text" name="title" size="30"placeholder="タイトル名"/></p>
          <p>Price :<input type="text" name="price" size="30" placeholder="値段" /></p>
          <p><input type="submit" value="Send" /></p>
        </form>
      </div>
      <div class="indexGet">
        <p>本の数 : {{.num}}</p>
        <p>合計金額 : {{.sumPrice}}</p>
        <ul>
          {{range.books}}
          <li>
            Title:{{.Title}}、Price:{{.Price}}
            <label><a href="/edit/{{.ID}}">編集</a></label>
            <label><a href="/delete_confirm/{{.ID}}">削除</a></label>
          </li>
          {{end}}
        </ul>
      </div>
    </div>
  </body>
</html>
```
```edit.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>編集ページ</title>
  </head>
  <body>
    <header>
      <h1>編集</h1>
    </header>
    <div class="wrap">
      <form action="/update/{{.book.ID}}" method="post">
        <p>Title：<input type="text" name="title" value="{{.book.Title}}" /></p>
        <p>Price: <input type="text" name="price" value="{{.book.Price}}" /></p>
        <p><input type="submit" value="Send"></p>
      </form>
    </div>
  </body>
</html>
```

```delete.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>削除ページ</title>
  </head>
  <body>
    <h1>削除確認</h1>
    <p>本当に削除しますか？</p>
    <ul>
      <li>Title : {{.book.Title}}</li>
      <li>Price : {{.book.Price}}</li>
    </ul>

    <form action="/delete/{{.book.ID}}" method="post">
      <p><input type="submit" value="削除"></p>
      <p><a href="/">戻る</a></p>
    </form>
  </body>
</html>
```

[__【Go / Gin で超簡単なWebアプリを作る】__](https://qiita.com/hyo_07/items/59c093dda143325b1859)を参考にさせていただきました。

今回は自分が新しく加えた箇所に関してご説明させていただきます。

# 本の合計冊数を取得する
```main.go
func dbGetNum() int {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	defer db.Close()
	if err != nil {
		panic("You can't open DB (dbGetNum())")
	}
	var num int
	db.Table("books").Count(&num)
	return num
}
```
新しく`dbGetNum()`を加えました。
int型のnumを宣言し、`db.Table("books").Count(&num)`でbooksテーブルにあるデータの数を取得し、取得した値を`return num`で返します。詳しくは[こちら](http://gorm.io/docs/query.html#Count)

#本の合計金額を取得する
```main.go
type Result struct {
	Total int
}
```

```main.go
func dbGetPrice() Result {
	db, err := gorm.Open("sqlite3", "book.sqlite3")
	defer db.Close()
	if err != nil {
		panic("You can't open DB (dbGetPrice())")
	}
	var result Result
	db.Table("books").Select("sum(price) as total").Scan(&result)
	return result
}
```
まず新しく構造体を宣言します。
そして、` db.Table("books").Select("sum(price) as total").Scan(&result)`でbooksテーブルのpriceカラムの合計値を、新しく宣言した構造体のフィールドに保存しています。詳しくは[こちら](http://gorm.io/docs/query.html#Group-amp-Having)

#main関数
```main.go
//Index
	router.GET("/", func(c *gin.Context) {
		books := dbGetAll()
		num := dbGetNum()
		sumPrice := dbGetPrice()
		c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})
	})
```

`num`と`sumPrice`にそれぞれ上述した関数を用いて値を代入しています。
そしてその値を、`c.HTML(200, "index.html", gin.H{"books": books, "num": num, "sumPrice": sumPrice.Total})`としてHTMLに渡しています。ちなみに`c.HTML(200...`の200はHTTPステータスコードといい、処理が成功して正常にレスポンスができている状態を意味します。詳しくは[こちら](https://digitalidentity.co.jp/blog/seo/seo-tech/howto-http-status-code.html)

#みなさんお気づきでしょうか...
<img width="665" alt="スクリーンショット 2019-03-31 21.02.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/343290/51f484fd-3b54-fdc0-6ceb-a921aa4df92e.png">
~~削除処理をした後に画面上から本の表示は消えているのに、合計冊数と合計金額はデータベース上に残ったままというバグが発生しています...。何度ググっても、何度コードを見返しても解決できませんでした...。もし、golangやgormに詳しい方がいらっしゃいましたら、何卒アドバイスをよろしくお願い致します。~~
無事に解決することができました。
解決法はコメント欄の方に載っております。
x-colorさんありがとうございます。

#リファレンス
 - [Go / Gin で超簡単なWebアプリを作る](https://qiita.com/hyo_07/items/59c093dda143325b1859)
 - [GORM](http://doc.gorm.io/)
 - [Package template](https://golang.org/pkg/html/template/#hdr-Contexts)
 - [Goのtemplateにおける値の評価の書き方](https://ema-hiro.hatenablog.com/entry/20170729/1501320887)