Kmszk with GO

PHPとかJavascriptとかGolangとかVim関連とか書きそうな感じがしていたが、現状Golangだらけになっている。

gormで実行されているクエリ(SQL文)を確認する

下記のように実行しているgormの裏側で実行されているクエリが知りたい場合には…

db.Where("id = ?", accountID).Find(&account)

Debug()を追加してあげると良い。

db.Debug().Where("id = ?", accountID).Find(&account)

gormでJOINを使う(Golang)

今回は、gormで返り値にJoinされる方のテーブルの値は取れているが、Joinする方のテーブルの値が取れていないときのTips。 結論から言うと、JOINを使う場合にはSelect()を指定することと、変数の型に注意。

gormでJoinを使うときの注意

当初はこんな感じで記述して取れていなかった。

commentList := []Comment{}
db.Where("entry_id = ?", entryID).Joins("INNER JOIN accounts on accounts.id = comments.account_id").Find(&commentList)

Findで指定している変数の型がこのままではダメそうなことに気づく

ということで下記のように修正。それでもダメだった。

type CommentJoinsAccount struct {
    Comment
    Account
}

commentList := []CommentJoinsAccount{}
db.Table("comments").Joins("INNER JOIN accounts ON accounts.id = comments.account_id").Where("entry_id = ?", entryID).Order("id desc").Limit(limit).Scan(&commentList)

※ Find()でテーブル指定していたのをScan()に変更したので、Table()を追加。
※ そしてScan()だとソフトデリートが自動化されないのね…『.Where("comments.deleted_at IS NULL")』を追加しました

ドキュメントの方ではSelectをわざわざ指定していることに気づく

CRUD: Reading and Writing Data · GORM Guide
Joinする方のテーブルのSelectを指定するのが必須だったみたい(しかもカラムも指定)。

type CommentJoinsAccount struct {
    Comment
    Account
}

commentList := []CommentJoinsAccount{}
db.Table("comments").Select("comments.*, accounts.name, accounts.icon").Joins("INNER JOIN accounts ON accounts.id = comments.account_id").Where("entry_id = ?", entryID).Order("id desc").Limit(limit).Scan(&commentList)

このとき、

.Select("comments.*, accounts.*")

でもダメみたいです。 なかなかにハマりました。

RevelのGetパラメータの取得方法の色々とroutesの関係

RevelでのGetパラメータの取得方法

RevelではGetパラメータの取得方法がいくつかある - Actionの引数にGetパラメータを指定する場合 - コントローラのBinderを利用する方法

Actionの引数にGetパラメータを指定する場合

Request Parameters and Binding | Revel - A Web Application Framework for Go!

func (c AppController) Action(name string) revel.Result {
    ...
}

この場合にはRevelのroutesを利用しようとすると引数指定を強制される。

routes.AppController.Action("kmszk") // -> ◯
routes.AppController.Action() // -> × ビルド時にエラーになる

コントローラのBinderを利用する方法

Request Parameters and Binding | Revel - A Web Application Framework for Go!

// Example params to binder
func (c SomeController) Action() revel.Result {
    var name string
    c.Params.Bind(&name, "name")
    ...
}

この場合にはRevelのroutesを利用しようとすると引数指定を強制されない。

routes.SomeController.Action() // -> ◯

Getパラメータが必須かどうか、指定することを強制するかどうかで使い分けると良さそう。

返り値で型指定するDuck Typing

Go言語では様々な形でダックタイピングが可能です。 今回はGo言語で利用可能な返り値の型指定を利用する例を紹介します。

package main

import (
    "fmt"
    "errors"
)

func main() {
    robot, err1 := GetDuck("robot")
    if err1 == nil {
        robot.Quack()
    }
 
    human, err2 := GetDuck("human")
    if err2 == nil {
        human.Quack()
    }
}

func GetDuck (ducktype string) (Duck, error) {
    if ducktype == "human" {
        return Human{}, nil
    } else if ducktype == "robot" {
        return Robot{}, nil
    }
 
    return nil, errors.New("正しいタイプを選んでね")
}

type Duck interface {
    Quack()
}

type Robot struct {}

func (r Robot) Quack () {
    fmt.Println("Q-U-A-C-K")
}

type Human struct {}

func (h Human) Quack () {
    fmt.Println("がーがー")
}

GetDuck()関数を通して構造体を取得することで、Human型とRobot型をDuck型として扱う事ができました。

個人的に最近使ったケースはOAuth1とOAuth2の認証部分を別々の構造体として実装し、呼び出し部分でGetDuck()の関数のようにOAuth型として取得し、コントローラ側から見たときにOAuth型としてOAuthのバージョンを意識しなくても良いような実装を行ってみたりしていました。

Revelの親コントローラを作成する

親コントローラ生成について

親コントローラ(ドキュメントではExtending the Controllerとなっているが)は下記のドキュメントのように作成すると良い。

Controllers | Revel - A Web Application Framework for Go!

type (
    BaseController struct {
        *revel.Controller
    }
)
type (
    MyController struct {
        BaseController
    }
)

このとき、

Note in the MyController the BaseController reference is NOT a pointers.

訳 : MyControllerでは、BaseController参照はポインタではありません

とのことなので注意。

またこのとき、ファイルを分けることもパッケージを分けることも可能なので、

controllers/core/baseController.go

package core

import (
    "github.com/revel/revel"
)

type (
    BaseController struct {
        *revel.Controller
    }
)

controllers/myController.go

package controllers

import (
    "github.com/revel/revel"
    "path/to/controllers/core"
)

type (
    MyController struct {
        BaseController
    }
)

という形でもOK。

Revelでリクエストのホスト名を取得

まずは上手く取れた記述から

package controllers

import (
    "github.com/revel/revel"
)

type Mycontroller struct {
    *revel.Controller
}


func (m *Mycontroller) Index() revel.Result {
    // redirectURLの取得
    requestedHost := m.Request.Host

    requestedScheme := "http"
    if m.Request.TLS != nil {
        requestedScheme = "https"
    }

    // ホスト名
    revel.TRACE.Println(requestedHost);

    // おまけにスキームも判断してみる(httpかhttpsか)
    revel.TRACE.Println(requestedScheme);
}

これで取れる理由

Controllerが継承している構造体を見ていくと、

github.com

type Controller struct {
    Name          string          // The controller name, e.g. "Application"
    Type          *ControllerType // A description of the controller type.
    MethodName    string          // The method name, e.g. "Index"
    MethodType    *MethodType     // A description of the invoked action type.
    AppController interface{}     // The controller that was instantiated.
    Action        string          // The fully qualified action name, e.g. "App.Index"

    Request  *Request
    Response *Response
    Result   Result

    Flash      Flash                  // User cookie, cleared after 1 request.
    Session    Session                // Session, stored in cookie, signed.
    Params     *Params                // Parameters from URL and form (including multipart).
    Args       map[string]interface{} // Per-request scratch space.
    RenderArgs map[string]interface{} // Args passed to the template.
    Validation *Validation            // Data validation helpers
}

Requestを継承している事がわかる。

Requestをもう少し見てみよう。

http - The Go Programming Language

type Request struct {

        // 〜中略〜

        // URL specifies either the URI being requested (for server
        // requests) or the URL to access (for client requests).
        //
        // For server requests the URL is parsed from the URI
        // supplied on the Request-Line as stored in RequestURI.  For
        // most requests, fields other than Path and RawQuery will be
        // empty. (See RFC 2616, Section 5.1.2)
        //
        // For client requests, the URL's Host specifies the server to
        // connect to, while the Request's Host field optionally
        // specifies the Host header value to send in the HTTP
        // request.
        URL *url.URL

        // 〜中略〜

        // For server requests Host specifies the host on which the
        // URL is sought. Per RFC 2616, this is either the value of
        // the "Host" header or the host name given in the URL itself.
        // It may be of the form "host:port".
        //
        // For client requests Host optionally overrides the Host
        // header to send. If empty, the Request.Write method uses
        // the value of URL.Host.
        Host string

        // 〜中略〜

        // RequestURI is the unmodified Request-URI of the
        // Request-Line (RFC 2616, Section 5.1) as sent by the client
        // to a server. Usually the URL field should be used instead.
        // It is an error to set this field in an HTTP client request.
        RequestURI string

        // 〜中略〜

        // TLS allows HTTP servers and other software to record
        // information about the TLS connection on which the request
        // was received. This field is not filled in by ReadRequest.
        // The HTTP server in this package sets the field for
        // TLS-enabled connections before invoking a handler;
        // otherwise it leaves the field nil.
        // This field is ignored by the HTTP client.
        TLS *tls.ConnectionState

        // 〜中略〜

}

Requestの中で、この3つが使えそうだ。

  • URL *url.URL
  • Host string
  • TLS *tls.ConnectionState

URL

まずはこれが良さそうなので、a.Request.URL.Hostとa.Request.URL.Schemeを取ってみるも、空の文字列が返ってくる。

理由はコメントに書いてあった。最近すこぶる性能が良くなったGoogle翻訳にコメント部分を突っ込んでみる

For server requests the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI.

サーバ要求の場合、URLはRequestURIに格納されているRequest-Lineで指定されたURIから解析されます。

ということで、RequestURIに格納されている文字列をParseしたものなので、そもそもRequestURIにはホスト名が含まれていなかったので得ることができなかった。

Host

これはホスト名を得ることが出来た。port番号が指定されていればportつきで取得できる。

TLS

上記でホスト名取得ができたがこちらも見ておく。

The HTTP server in this package sets the field for TLS-enabled connections before invoking a handler; otherwise it leaves the field nil.

このパッケージのHTTPサーバーは、ハンドラを呼び出す前にTLS対応接続のフィールドを設定します。 それ以外の場合は、フィールドはnilになります。

ほほう。TLSでの接続じゃない場合、nilになるとのこと。ということでここのnilチェックでhttpかhttpsが判別できそう。

ということで、ブログ冒頭のようにホスト名取得(とおまけにhttp/httpsの判別)ができた。

gormでdeleted_atつけてソフトデリートにする

私はGo言語での開発ではモデル周りはgorm使っています。
ソフトデリート利用するときにちょっとハマって、デフォルトで0000-00-00 00:00:00が入ってしまう人のためのTips。

以下のように記述するとOK

package models

import (
    "time"
)

type Table struct {
    ID      int
    CreatedAt       time.Time
    UpdatedAt       time.Time
    DeletedAt       *time.Time
}

DeletedAtだけ*time.Timeになることに注意。

デフォルトでカラムにnull以外が入っているとgormのソフトデリート的には削除済み扱いになってしまうみたいです。

始まりました

Qiitaなども書いていましたが

こちらに細々としたものとか書いていきたいと思います。