アプリケーションが備えているべき 4 つの機能を CRUD(クラッド)と言います。データの生成(Create)、データの読み取り(Read)、データの更新・変更(Update)、データの削除(Delete)の頭文字を並べたものです
この章では、Todo アプリに、まだ搭載されていない、更新を追加したいと思います。
次のコードで Gin、GORM、SQLite をインストールします。macOS、Mint(Ubunt)、CentOS 7で同じコードです。少し時間がかかる場合があります。
go get github.com/gin-gonic/gin
go get github.com/jinzhu/gorm
go get github.com/mattn/go-sqlite3
次のようなディレクトリ構成にします。
gin/
- main.go
- templates/
- - index.html
- - edit.html
- styles/
- - style.css
ディレクトリ構成、ディレクトリ名、ファイル名は自由に決めて大丈夫です。
package main
import (
"strconv" // for strconv.Atoi
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
/*
sqlite3 は読み込まなければなりません
しかし実際のデータベースの操作は、gorm で行います
そのため、sqlite3は使わないことを、 _ で明示しなければなりません
*/
_ "github.com/mattn/go-sqlite3"
)
// モデルの宣言(データ構造の宣言)
type Todo struct {
gorm.Model
Memo string
}
func main() {
r := gin.Default()
r.Static("styles", "./styles")
r.LoadHTMLGlob("templates/*.html")
dbInit()
// List
r.GET("/", func(c *gin.Context) {
todos := dbGetAll()
c.HTML(200, "index.html", gin.H{"todos": todos})
})
// Create
r.POST("/new", func(c *gin.Context) {
memo := c.PostForm("memo")
dbCreate(memo)
c.Redirect(302, "/")
})
//Delete
r.GET("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, "/")
})
// Edit
r.GET("/edit/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic(err)
}
todo := dbGetOne(id)
c.HTML(200, "edit.html", gin.H{"todo": todo})
})
// Update
r.POST("/update/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic(err)
}
memo := c.PostForm("memo")
dbUpdate(id, memo)
c.Redirect(302, "/")
})
// Run Server
r.Run() // 引数を指定しないと、":8080" が指定されたことになります
}
// データベースのマイグレート(データベースの初期化)
func dbInit() {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("failed to connect database\n")
}
defer db.Close()
db.AutoMigrate(&Todo{})
}
// データの作成
func dbCreate(memo string) {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("failed to connect database\n")
}
defer db.Close()
db.Create(&Todo{Memo: memo})
}
// データの削除
func dbDelete(id int) {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("failed to connect database\n")
}
defer db.Close()
var todo Todo
db.First(&todo, id)
db.Delete(&todo)
}
// データの更新
func dbUpdate(id int, memo string) {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("failed to connect database\n")
}
defer db.Close()
var todo Todo
db.First(&todo, id)
todo.Memo = memo
db.Save(&todo)
}
// データのすべてを取得
func dbGetAll() []Todo {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("failed to connect database\n")
}
defer db.Close()
var todos []Todo
db.Find(&todos)
return todos
}
// データの一つを取得
func dbGetOne(id int) Todo {
db, err := gorm.Open("sqlite3", "todo.sqlite3")
if err != nil {
panic("railed to connect database\n")
}
defer db.Close()
var todo Todo
db.First(&todo, id)
return todo
}
初期化のところで、マイグレート(migrate)という用語が出てきました。普通の初期化は、データをすべて書き替えますが、マイグレートは、データベースに保存されているデータを保持したまま、データの追加・削除・変更を行います。データファイルがなければ新しいデータファイルを作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Todo</title>
<link rel="stylesheet" href="../styles/style.css">
</head>
<body>
<h1>Todo</h1>
<div align="center">
<form method="post" action="/new">
<p><font size="-1">メモ</font>
<input type="text" name="memo" size="30" placeholder="入力してください">
<input type="submit" value="登録"></p>
</form>
</div>
<div class="container">
<ul>
{{ range .todos }}
<li>{{.Memo}}
<!-- 編集 -->
<label>
<a href="/edit/{{.ID}}">[編集]</a>
</label>
<!-- 削除 -->
<label>
<a onclick="var ok=confirm('本当に削除しますか?');
if (ok) location.href='delete/{{.ID}}'; return false;"><span class="delete">[削除]</span></a>
</label>
</li>
{{ end }}
</ul>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Edit</title>
<link rel="stylesheet" href="../styles/style.css">
</head>
<body>
<h1>Edit</h1>
<div align="center">
<form action="/update/{{.todo.ID}}" method="post">
<p><font size="-1">メモ</font>
<input type="text" name="memo" size="30" value="{{.todo.Memo}}">
<input type="submit" value="送信"></p>
</form>
</div>
</body>
</html>
body {
color: #666;
background: darkgrey;
}
h1 {
text-align: center;
font-family: sans-serif;
font-weight: 100;
border-bottom: 1px solid lightgray;
}
.container {
width: 330px;
margin: auto;
font-size: 14px;
}
a {
color: #666;
font-size: 12px;
text-decoration: none;
}
ul {
font-size: 14px;
}
.delete {
font-size: 12px;
cursor: pointer;
}
実行する場合は、ターミナルで gin ディレクトリに移動して、go run main.go と入力して、エンターキーを押します。
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /styles/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] HEAD /styles/*filepath --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
[GIN-debug] Loaded HTML Templates (3):
-
- edit.html
- index.html
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] POST /new --> main.main.func2 (3 handlers)
[GIN-debug] GET /delete/:id --> main.main.func3 (3 handlers)
[GIN-debug] GET /edit/:id --> main.main.func4 (3 handlers)
[GIN-debug] POST /update/:id --> main.main.func5 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
Gin の場合は、多くのコードが表示されます。
macOS の場合は、次のようなアラートが表示される場合があります。「許可」をクリックしてください。
ブラウザで localhost:8080/ にアクセスすると次のように表示されます。
[編集] をクリックすると編集ページに移動します。