Go言語

Go言語Webアプリ開発日記(2日目)

投稿日:

今日は、前回作ったWebアプリを少し手直ししながら作成していきます。

尚、Go言語を書き直すたびに、Ctrl+Cでサーバーを止めてから再度 go run server.goを入力するのは、面倒なので、自動コンパイルパッケージを導入していきたいと思います。

自動コンパイル

インストールは簡単で、ターミナルから下記のコマンドを入力します。

go get github.com/codegangsta/gin

そして次回からサーバーを起動するときに、下記のコマンドを入力するだけです。

gin -i run server.go

これで、ファイルを書き直すたびに自動コンパイルしてくれます。Go言語はコンパイルが早いので、PHPのようなスクリプト言語のように手直し修正が簡単にできます。これが私がGo言語を学習しようと思った一番の理由です。

GormでCRUD

今日は、GormでCRUDの勉強をします。

Create(データの作成)

まず最初にデータを作成します。今回は、ユーザーを作成しますので、mainパッケージのserver.goを下記のように修正します。

  • server.go
r := gin.Default()
	r.LoadHTMLGlob("views/*.html")
	v1 := r.Group("/users")
	{
		v1.GET("/create", c.GetUserCreate)
		v1.POST("/create", c.PostUserCreate)
        }
  • 3行目:URLでusers-indexやusers-createとするところをusers/indexやusers/createとするためにグループを作成します。
  • r.Group("/users")の{}の中にルーティングを記述します。
  • 4行目にGETでの処理を記述し、5行目にPOSTでの処理を記述しています。これをv1.Any()として、関数内で振り分ける方法もあると思いますが、こちらの方が処理が早いと思いますので、別々に記述します。
  • 尚、ルーターを別に作成して、middleweareとして読み込ませる方法もあると思いますが、それは後日調べてみたいと思います。

それでは、GetUserCreate関数とPostUserCreate関数を作成します。

  • controllers/user.go
package controllers

import (
	"github.com/gin-gonic/gin"
	"net/http"
    m "myapp/models"
)
var user m.User
func GetUserCreate(c *gin.Context) {
   c.HTML(http.StatusOK, "user-create.html", "")
}
func PostUserCreate(c *gin.Context) {
	user.Name = c.PostForm("name")
	user.Email = c.PostForm("email")
	user.Create(&user)
	c.Redirect(http.StatusMovedPermanently,"/users")
}
  • GetUserCreate関数は、添付データもなしで単純にビューファイルを開くだけですので、第3引数には""を入力しています。
  • 12行目はmodelsパッケージのUser構造体を初期化して、変数userに代入しています。
  • 15行目はmodelsパッケージのUserCreateメソッドを実行します。
  • 16行目は"/users"、つまりGetUsers関数へリダイレクトします。Redirectメソッドの第1引数は、ステータスコードの301です。

modelsパッケージのUserCreateメソッドを下記のように記述します。

func (u User)Create(user *User) {
	db := GormDB()
	/* 同姓同名がいないかを検索する */
	db.Where("name = ?",u.Name).FirstOrCreate(&u)
	db.Save(&u)
}

ただ、このままだと各メソッドごとにdb := GormDB()を実行しないといけません。そこで、毎回実行しなくてもいいように、modelsパッケージのgorm.goファイルを下記のように修正します。下記のように修正することで、上記の2行目は削除することができます。

package models

import (
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"log"
)

var db *gorm.DB

func init() {
	db = gormDB()
}

func gormDB() *gorm.DB {
	DBMS     := "mysql"
	USER     := "root"
	PASS     := "mypassword"
	PROTOCOL := "tcp(127.0.0.1:3306)"
	DBNAME   := "GODB"
	TIME 	 := "?parseTime=true"

	CONNECT := USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME + TIME
	db,err := gorm.Open(DBMS, CONNECT)

	if err != nil {
		panic(err.Error())
	}
	db.LogMode(true)
	db.AutoMigrate(&User{})

	log.Println("Connected to database.")
	return db
}

ビューファイルを下記のように記述します。

  • views/user-create.html
{{template "header"}}
{{template "navbar"}}
   <form action="/users/create" method="post">
       <label>氏名</label>
           <input type="text" name="name">
       <label>Eメール</label>
           <input type="text" name="email">
      <input type="submit" value="作成">
   </form>
{{template "footer"}}
  • フォームのアクションは、action="create"でも、今回は大丈夫ですが、移動元によってはエラーになりますので、上記のようにaction="/users/create"にして置いたほうが無難です。
  • methodは必ずpostに指定してください。

 

Read(データの読み込み)

CRUDのReadに関しては、前回紹介していますので、省略させていただきます。

要はデータの一覧を取得してビューに表示するという単純な作業です。

Update(データーの修正)

次は、CRUDのUpdateです。Updateするには、データの一覧ビューにデータを修正するためのページへのリンクを作成します。

それでは、index.htmlとserver.goの一部を下記のように修正追加します。

  • views/index.html
{{range .}}
  <tr>
     <td><a href="/users/edit/{{.ID}}">{{.Name}}</a></td>
     <td>{{.Email}}</td>
     <td>{{.CreatedAt}}</td>
  </tr>
{{end}}
  • server.go
v1 := r.Group("/users")
{
    v1.GET("/edit/:id", c.GetUserEdit)
    v1.POST("/edit", c.PostUserEdit)
}

コントローラのGetUserEditとPostUserEditを下記のように作成します。

  • controllers/user.go
func GetUserEdit(c *gin.Context) {
	id,_ := strconv.Atoi(c.Param("id"))
	user := user.GetUser(id)
	c.HTML(http.StatusOK, "user-edit.html", user)
}
func PostUserEdit(c *gin.Context) {
	user.ID,_ = strconv.Atoi(c.PostForm("id"))
	user.Name = c.PostForm("name")
	user.Yomi = c.PostForm("yomi")
	user.Email = c.PostForm("email")
	user.Edit(&user)
	c.Redirect(http.StatusMovedPermanently,"/users")
}
  • 2行目のgin.Context構造体のParamメソッドは、パラメーターの値を文字列として返します。
  • その返された文字列をstrconvパッケージのAtoi関数で数値に変換します。このAtoi関数は、指定された文字列を数値に変換し、その数値とエラーを返します。本来ならエラー判定をしてから値を取得するのでしょうが、数値のみを取得するために、id, _ := としてidのみを取得するようにしています。
  • 3-4行目:modelsのUser構造体を初期化し、GetUserメソッドを実行し返されたデータをuser変数に代入しています。
  • 5行目:ビューのuser-edit.htmlにuserデータを受け渡しています。

それでは、最後にmodelsのGetUserメソッドとUserEditメソッドを下記の記述しておきます。

func (u User) GetUser(id int) *User {
	user := &u
	user.ID = id
	db.Unscoped().First(user)
	return user
}
func (u User) Edit(user *User) {
	db.Model(&user).Update(u)
}
  • 5行目のUnscopedメソッドは、ソフトデリートを無効化するメソッドです。全データを取得するときは、ソフトデリートが有効で、削除データは表示されませんが、idで指定したときは、ソフトデリートに関係なくデータを取得したいからです。

Delete(データの削除)

CRUDの最後は、Delete(データの削除)です。Gormを使うからには、ソフトデリートは便利機能ですのでソフトデリートも合わせて調べていきます。

まずは、ルーティングです。削除用の関数と、リストア用の関数、そして完全削除用の関数を指定します。

  • server.go
v1 := r.Group("/users")
{
   v1.POST("/delete",c.PostUserDelete)
   v1.POST("/restore", c.PostUserRestore)
   v1.POST("/forcedelete", c.PostUserForceDelete)
}

ビューファイルの作成は、既存の編集用のビューに下記のようなボタンを追加します。

{{if .DeletedAt}}
<h2>{{.Name}}のデータ処理
<form action="/users/restore" method="post">
  <input type="hidden" name="id" value="{{.ID}}">
  <input type="submit" value="復元">
  <a data-target="#force-delete" data-toggle="modal" class="btn">完全削除</a>
</form>
{{else}}
<form action="/users/edit" method="post">
  <input type"text" name="name" value="{{.Name}}">
  <input type="text" name="email" value="{{.Email}}">
  <input type="hidden" name="id" value="{{.ID}}">
  <input type="submit" value="修正" class="btn">
  <a data-target="#delete" data-toggle="modal" class="btn">削除</a>
</form>
{{end}}
  • {{if .DeletedAt}}から{{else}}迄で、ソフトデリートされたデータの場合のための処理ボタンを表示します。そして{{else}}以下で、ソフトデリートされていないデータの場合の処理ボタンを表示しています。
  • 削除処理、及び完全削除処理はモーダルウィンドウが開くようにしています。開いたモーダルウインドウにフォームを作成して削除処理用のactiomと完全削除用のactionを作成するようにしていますが、この辺のところは省略します。

コントローラーを下記のように記述します。

  • controllers/user.go
func PostUserDelete(c *gin.Context) {
	id := c.PostForm("id")
	user.ID,_ = strconv.Atoi(id)
	user.Delete(&user)
	c.Redirect(http.StatusMovedPermanently,"/users")
}
func PostUserRestore(c *gin.Context) {
	id := c.PostForm("id")
	user.ID,_ = strconv.Atoi(id)
	user.Restore(&user)
	c.Redirect(http.StatusMovedPermanently,"/users")
}
func PostUserForceDelete(c *gin.Context) {
	user.ID,_ = strconv.Atoi(c.PostForm("id"))
	user.ForceDelete(&user)
	c.Redirect(http.StatusMovedPermanently,"/users")
}

モデルを下記のように作成します。

  • models/user.go
/* データの値からUserをソフトデリートする */
func (u User) Delete(user *User) {
	db.First(user.ID)
	db.Delete(&user)
}
/* データの値からUserを完全削除する */
func (u User) ForceDelete(user *User) {
	db.Unscoped().First(user.ID)
	db.Unscoped().Delete(&user)
}
/* ソフトデリートされたUsersを取得する */
func (u User) Trashed() *[]User {
	users := &[]User{}
	db.Unscoped().Where("deleted_at IS NOT NULL").Find(users)
	return users
}
/* ソフトデリートされたUserを復元する */
func (u User) Restore(user *User) *User {
	u.ID = user.ID
	db.Unscoped().First(&u).Update("deleted_at",nil)
	return user
}

 

 

 

 

-Go言語

Copyright© WinRoad徒然草 , 2018 All Rights Reserved Powered by AFFINGER5.