よしかわーるど

プログラミングで世界を変える

Treasure2018 2日目 Golang

Treasure2018 2 日目

今日は Golang 入門 2 日目

やったこと

簡単な Web アプリを Golang で書けることが出来た 🎉🎉

TODO アプリの拡張。

SQLGolang を繋げる作業。

SQL を叩いて Golang でパースすることが出来ました。

わからなかったところ

最後の TODO アプリの検索の仕方で手間取りました。

title と status で検索したかったけど、片方だけの検索しか出来なかった。

title と status の同時検索が出来なかった。

curl http://localhost:8080/api/todos/search?title=test\&completed=0

controller/task.goファイル

package controller

import (
    "encoding/json"
    "net/http"

    "github.com/voyagegroup/go-todo/model"

    "github.com/jmoiron/sqlx"
)

// Todo はTodoへのリクエストに関する制御をします
type Todo struct {
    DB *sqlx.DB
}

// GetはDBからユーザを取得して結果を返します
func (t *Todo) Get(w http.ResponseWriter, r *http.Request) error {
    todos, err := model.TodosAll(t.DB)
    if err != nil {
        return err
    }
    return JSON(w, 200, todos)
}

func (t *Todo) Put(w http.ResponseWriter, r *http.Request) error {
    var todo model.Todo
    if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
        return err
    }

    if err := TXHandler(t.DB, func(tx *sqlx.Tx) error {
        result, err := todo.Update(tx)
        if err != nil {
            return err
        }
        if err := tx.Commit(); err != nil {
            return err
        }
        todo.ID, err = result.LastInsertId()
        return err
    }); err != nil {
        return err
    }

    return JSON(w, http.StatusOK, todo)
}

// PostはタスクをDBに追加します
// todoをJSONとして受け取ることを想定しています。
func (t *Todo) Post(w http.ResponseWriter, r *http.Request) error {
    var todo model.Todo
    if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
        return err
    }

    if err := TXHandler(t.DB, func(tx *sqlx.Tx) error {
        result, err := todo.Insert(tx)
        if err != nil {
            return err
        }
        if err := tx.Commit(); err != nil {
            return err
        }
        todo.ID, err = result.LastInsertId()
        return err
    }); err != nil {
        return err
    }

    return JSON(w, http.StatusCreated, todo)
}

func (t *Todo) Delete(w http.ResponseWriter, r *http.Request) error {
    var todo model.Todo
    if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
        return err
    }

    if err := TXHandler(t.DB, func(tx *sqlx.Tx) error {
        _, err := todo.Delete(tx)
        if err != nil {
            return err
        }
        return tx.Commit()
    }); err != nil {
        return err
    }

    return JSON(w, http.StatusOK, todo)
}

func (t *Todo) Toggle(w http.ResponseWriter, r *http.Request) error {
    var todo model.Todo

    if err := json.NewDecoder(r.Body).Decode(&todo); err != nil {
        return err
    }

    if err := TXHandler(t.DB, func(tx *sqlx.Tx) error {
        _, err := todo.Toggle(tx)
        if err != nil {
            return err
        }
        return tx.Commit()
    }); err != nil {
        return err
    }

    return JSON(w, http.StatusOK, todo)
}

func (t *Todo) Search(w http.ResponseWriter, r *http.Request) error {
    title := r.URL.Query().Get("title")
    completed := r.URL.Query().Get("completed")

    todo, err := model.SearchByCompleted(t.DB, completed)
    if err != nil {
        return err
    }

    return JSON(w, http.StatusOK, todo)
}

model/task.go ファイル

package model

import (
    "database/sql"
    "time"

    "github.com/jmoiron/sqlx"
)

// Todoは管理するタスク
type Todo struct {
    ID        int64      `db:"todo_id" json:"id"`
    Title     string     `json:"title"`
    Completed bool       `json:"completed"`
    Created   *time.Time `json:"created"`
    Updated   *time.Time `json:"updated"`
}

func TodosAll(dbx *sqlx.DB) (todos []Todo, err error) {
    if err := dbx.Select(&todos, "select * from todos"); err != nil {
        return nil, err
    }
    return todos, nil
}

func TodoOne(dbx *sqlx.DB, id int64) (*Todo, error) {
    var todo Todo
    if err := dbx.Get(&todo, `
  select * from todos where todo_id = ?
  `, id); err != nil {
        return nil, err
    }
    return &todo, nil
}

// TodosToggleAllは全部のtoggleのステータスをトグルします
func TodosToggleAll(tx *sqlx.Tx, checked bool) (sql.Result, error) {
    stmt, err := tx.Prepare(`
  update todos set completed = ?
  `)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec(checked)
}

func (t *Todo) Update(tx *sqlx.Tx) (sql.Result, error) {
    stmt, err := tx.Prepare(`
  update todos set title = ? where todo_id = ?
  `)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec(t.Title, t.ID)
}

func (t *Todo) Insert(tx *sqlx.Tx) (sql.Result, error) {
    stmt, err := tx.Prepare(`
  insert into todos (title, completed)
  values(?, ?)
  `)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec(t.Title, t.Completed)
}

// Toggle は指定されたタスクについて現在の状態と入れ替えます。
func (t *Todo) Toggle(tx *sqlx.Tx) (sql.Result, error) {
    stmt, err := tx.Prepare(`
  update todos set completed=?
  where todo_id=?
  `)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec(!t.Completed, t.ID)
}

func (t *Todo) Delete(tx *sqlx.Tx) (sql.Result, error) {
    stmt, err := tx.Prepare(`delete from todos where todo_id = ?`)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec(t.ID)
}

// TodosDeleteAllはすべてのタスクを消去します。
func TodosDeleteAll(tx *sqlx.Tx) (sql.Result, error) {
    stmt, err := tx.Prepare(`delete from todos where completed = 1`)
    if err != nil {
        return nil, err
    }
    defer stmt.Close()
    return stmt.Exec()
}

func SearchByTitle(dbx *sqlx.DB, title string) ([]*Todo, error) {
    var todos []*Todo

    if err := dbx.Select(&todos, "select * from todos where title = ?", title); err != nil {
        return nil, err
    }

    return todos, nil
}

func SearchByCompleted(dbx *sqlx.DB, completed string) ([]*Todo, error) {
    var todos []*Todo

    if err := dbx.Select(&todos, "select * from todos where completed = ?", completed); err != nil {
        return nil, err
    }

    return todos, nil
}

server.go に以下を追記

router.Handle("/api/todos/search", handler(todo.Search)).Methods("GET")

memo

SQL の使い方は以下の URL を参考にする。

https://github.com/go-sql-driver/mysql/wiki/Examples

http://go-database-sql.org/accessing.html

https://github.com/jmoiron/sqlx

https://github.com/variadico/scaneo