なんとなくGo言語を書いてみたくなった。
Mac環境でやります。
劣化版Twitterアプリみたいなのを作ります。
ユーザー認証とかはないです。
今回は、
・ 一覧・詳細表示、登録、削除、更新
・ 空の投稿ができないようにするバリデーション
を実装していきます。
Go言語とは?
これによると、
並列処理、ガベージコレクションを備え、軽快にコンパイルできる言語です。以下のような特徴を持っています:
- 一台のコンピュータ上であっという間に大型のGoプログラムをコンパイルすることができます。
- Goはソフトウェアの構造にモデルを与えます。分析をより簡単にこなすことができ、ファイルやライブラリのincludeといったCスタイルの書き出しにありがちな部分を大幅に省くことができます。
- Goは静的型付け言語です。型に階層の概念が無いのでユーザはその関係に気をとられることもなく、典型的なオブジェクト指向言語よりももっとライトに感じるくらいです。
- Goは完全なガベージコレクションタイプの言語です。また、基本的な並列処理とネットワークをサポートしています。
- Goはマルチプロセッサ対応のソフトウェアを作成できるようデザインされています。
完全に理解した。
ginとは
go言語のフレームワークです。railsとかと違って密結合なオールインワンではないです。
なので、railsやってた方は拍子抜けするかも。
また、これくらいの規模ならFW使わなくても手軽にwebサービス作れちゃうのがgo言語のいい所だと理解しています。
gormとは
GO言語用のORMフレームワークです。
DB周りの処理を実装する時にこのフレームワークを使うと便利です。
ORMを使わないと、SQL文をコード内に書かないといけなくなるので、ぜひORM使いましょう。
ちなみにrailsだとActive RecordがORMにあたります。
ORMの恩恵がいまいちピンとこない方はこちらの記事をみるといいでしょう。
RailsのORM機能について
GoをローカルPCに入れてみる。
ここ見てやってみてください。
ターミナルでgo
って二文字を打って、以下のようになってたらOK。
おめでとう!
$ go
Go is a tool for managing Go source code.
Usage:
go <command> [arguments]
The commands are:
bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
mod module maintenance
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet report likely mistakes in packages
Use "go help <command>" for more information about a command.
Additional help topics:
buildmode build modes
c calling between Go and C
cache build and test caching
environment environment variables
filetype file types
go.mod the go.mod file
gopath GOPATH environment variable
gopath-get legacy GOPATH go get
goproxy module proxy protocol
importpath import path syntax
modules modules, module versions, and more
module-get module-aware go get
module-auth module authentication using go.sum
module-private module configuration for non-public modules
packages package lists and patterns
testflag testing flags
testfunc testing functions
Use "go help <topic>" for more information about that topic.
MySQLをローカルにいれる。
railsとかと違って、MySQLは自分で用意しないといけない。はず。
以下のコードをターミナルで打ってMySQLに入れたらもうインストール済です。よかったですね。
$ mysql -uroot -p
入れなかった人は、インストールしましょう。
某はこちらの記事を見てインストールしました。
【超簡単】macへMySQLをインストール
ありがたやー。
インストールできたらターミナル画面はそのままで次へ
MySQLセットアップ
次はデータベースを作ったり、ユーザーを作ったりします。
データベース名:test
ユーザー名:test
パスワード:12345678
以下ターミナルに打ち込んでください。
// データベース作成
mysql> create database test;
// ユーザー作成
mysql> create user 'test'@'localhost' IDENTIFIED BY '12345678';
// データベースにアクセスする権限をユーザーに付与
mysql> grant all privileges on test.* to 'test'@'localhost';
mysql> flush privileges;
mysql> exit
テーブル作らなくていいの?と思った方。
後述のmain.go内のdbInit()でautoMigrateを使ってTweetsテーブルを自動で作ってます。
他にテーブルが必要な場合は、autoMigrateに都度書いていくという感じです。
ディレクトリ構成図
cd $GOPATH/src
して、以下のディレクトリやファイルを作ってください。
ファイルの中身は今は空で構いません。
mytweet/
┣ views/
┃ ┣ delete.html
┃ ┣ detail.html
┃ ┣ index.html
┗ main.go
外部ライブラリの導入
go get
というコマンドを使います。
外部ライブラリを導入するには、go getコマンドを利用すると便利です。
go get パッケージ名
go getコマンドを発行すると、以下の処理が自動的に行われます。
・指定したパッケージのGitリモートリポジトリを$GOPATH/srcへダウンロード
・依存パッケージのGitリモートリポジトリを$GOPATH/srcへダウンロード
・ソースコードのビルド(go installコマンド相当)
参照:はじめてのGO言語
以下ターミナルで実行
// ginフレームワーク
$ go get github.com/gin-gonic/gin
// mysql用ドライバー
$ go get github.com/go-sql-driver/mysql
// gorm
$ go get github.com/jinzhu/gorm
go getした際にソースはGOPATH/src配下に、インストールされます。
ちなみに、実行ファイル(コンパイルされたやつ)はGOPATH/binにインストールされます。
完成版ソースコード一気見せ
完成したソースコードを載せます。いきなり雑になってごめんなさい。
さっき作成したファイルにコピペしてください。
package main
import (
"log"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql" //直接的な記述が無いが、インポートしたいものに対しては"_"を頭につける決まり
"github.com/jinzhu/gorm"
)
// Tweetモデル宣言
// モデルはDBのテーブル構造をGOの構造体で表したもの
type Tweet struct {
gorm.Model
Content string `form:"content" binding:"required"`
}
func gormConnect() *gorm.DB {
DBMS := "mysql"
USER := "test"
PASS := "12345678"
DBNAME := "test"
// MySQLだと文字コードの問題で"?parseTime=true"を末尾につける必要がある
CONNECT := USER + ":" + PASS + "@/" + DBNAME + "?parseTime=true"
db, err := gorm.Open(DBMS, CONNECT)
if err != nil {
panic(err.Error())
}
return db
}
// DBの初期化
func dbInit() {
db := gormConnect()
// コネクション解放解放
defer db.Close()
db.AutoMigrate(&Tweet{}) //構造体に基づいてテーブルを作成
}
// データインサート処理
func dbInsert(content string) {
db := gormConnect()
defer db.Close()
// Insert処理
db.Create(&Tweet{Content: content})
}
//DB更新
func dbUpdate(id int, tweetText string) {
db := gormConnect()
var tweet Tweet
db.First(&tweet, id)
tweet.Content = tweetText
db.Save(&tweet)
db.Close()
}
// 全件取得
func dbGetAll() []Tweet {
db := gormConnect()
defer db.Close()
var tweets []Tweet
// FindでDB名を指定して取得した後、orderで登録順に並び替え
db.Order("created_at desc").Find(&tweets)
return tweets
}
//DB一つ取得
func dbGetOne(id int) Tweet {
db := gormConnect()
var tweet Tweet
db.First(&tweet, id)
db.Close()
return tweet
}
//DB削除
func dbDelete(id int) {
db := gormConnect()
var tweet Tweet
db.First(&tweet, id)
db.Delete(&tweet)
db.Close()
}
func main() {
router := gin.Default()
router.LoadHTMLGlob("views/*.html")
dbInit()
//一覧
router.GET("/", func(c *gin.Context) {
tweets := dbGetAll()
c.HTML(200, "index.html", gin.H{"tweets": tweets})
})
//登録
router.POST("/new", func(c *gin.Context) {
var form Tweet
// ここがバリデーション部分
if err := c.Bind(&form); err != nil {
tweets := dbGetAll()
c.HTML(http.StatusBadRequest, "index.html", gin.H{"tweets": tweets, "err": err})
c.Abort()
} else {
content := c.PostForm("content")
dbInsert(content)
c.Redirect(302, "/")
}
})
//投稿詳細
router.GET("/detail/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic(err)
}
tweet := dbGetOne(id)
c.HTML(200, "detail.html", gin.H{"tweet": tweet})
})
//更新
router.POST("/update/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic("ERROR")
}
tweet := c.PostForm("tweet")
dbUpdate(id, tweet)
c.Redirect(302, "/")
})
//削除確認
router.GET("/delete_check/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic("ERROR")
}
tweet := dbGetOne(id)
c.HTML(200, "delete.html", gin.H{"tweet": tweet})
})
//削除
router.POST("/delete/:id", func(c *gin.Context) {
n := c.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic("ERROR")
}
dbDelete(id)
c.Redirect(302, "/")
})
router.Run()
}
<!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">
<div class="input">
<p>{{.err}}</p>
<form action="/new" method="post">
<p>いま思っていること :<input type="text" name="content" size="30" placeholder="つぶやくこと"/></p>
<p><input type="submit" value="つぶやく" /></p>
</form>
</div>
<div class="indexGet">
<ul>
{{range.tweets}}
<li>
{{.Content}}
<label><a href="/detail/{{.ID}}">編集</a></label>
<label><a href="/delete_check/{{.ID}}">削除</a></label>
</li>
{{end}}
</ul>
</div>
</div>
</body>
</html>
<body>
<h2>詳細</h2>
<form method="post" action="/update/{{.tweet.ID}}">
<p>内容<input type="text" name="tweet" size="30" value="{{.tweet.Content}}" ></p>
<p><input type="submit" value="Send"></p>
</form>
</body>
<body>
<h1>削除確認</h1>
<p>本当に削除しますか?</p>
<ul>
<li>内容: {{.tweet.Content}}</li>
<li>作成時間: {{.tweet.CreatedAt}}</li>
</ul>
<form method="post" action="/delete/{{.tweet.ID}}">
<p><input type="submit" value="削除"></p>
<p><a href="/">戻る</a></p>
</form>
</body>
起動
mytweetディレクトリに入って、以下を実行!
go run main.go
http://localhost:8080
を開いて、できてたらOK!
go run
コマンドとは?
Go言語はコンパイル言語ですが、go runコマンドを用いると一時ファイルとしてコンパイルしたプログラムをその場で実行することができます。
実行ファイルの作り方などはここを参照しましょう。
最後に
バリデーションメッセージがrailsのように親切じゃない。
自分でメッセージも用意しないといけないのかな。