20210221のGoに関する記事は9件です。

Sqlboilerの設定ファイルがうまく読み込まれない

Sqlboilerの設定が正しいはずなのに読み込まれない。。

Go言語でSqlboilerを採用したのですが、設定周りで結構ハマってしまってしまったので、備忘録として記載します。
TOMLの設定正しいのに、なぜか読んでくれない。このエラー出る。。って人は参考にしてください。

found key user in config, but it was not a string (<nil>)

SQLBoiler?

Go言語のORMです。
GORMと、SQLBoilerが大体有名どころみたいです。
GORMは、モデルファーストなフレームワークで評判も良さそうですが、すでにテーブル定義があったので、スキーマファーストらしいSQLBoilerをORMに使うことにしました。

使ってみる

sqlboiler.toml(yamlでもいいらしい)をルートに配置して、DBの接続設定を書くと、DBに接続してスキーマからモデルを生成してくれるツールです。
やりかたは公式のReadmeに詳しく載っています。いざ、tomlを書いて、mysqlに接続してみます。

tomlはこんな感じ。mysqlのテーブルに接続設定を書けば良いと。
TOMLの構文はこちらが詳しいです。

output = "repository/models"
pkgname = "repository"
#delete output directory if exists.
wipe = "true"
debug = "true"

[mysql]
dbname = "db-name"
host = "127.0.0.1"
port = 3306
user = "user"
pass = "pass"
sslmode = "false"

以下コマンドで生成!

sqlboiler mysql

すると。。。

found key user in config, but it was not a string (<nil>)

ええ、、
なんでやねん。

-dをつけて、デバック実行とやらをやってみると、

{
    "config": {
        "driver_name": "mysql",
        "driver_config": {
            "blacklist": null,
            "dbname": null,
            "host": null,
            "pass": null,
            "port": null,
            "sslmode": null,
            "user": null,
            "whitelist": null
        },
        "pkg_name": "repository",
        "out_folder": "models",
        "debug": true,
        "wipe": true,
        "struct_tag_casing": "snake",
        "relation_tag": "-",
        "imports": {
            "all": {
                "Standard": [
                    "\"database/sql\"",
                    "\"fmt\"",
                    "\"reflect\"",
                    "\"strings\"",
                    "\"sync\"",
                    "\"time\""
                ],
                "ThirdParty": [
                    "\"github.com/friendsofgo/errors\"",
                    "\"github.com/volatiletech/sqlboiler/v4/boil\"",
                    "\"github.com/volatiletech/sqlboiler/v4/queries\"",
                    "\"github.com/volatiletech/sqlboiler/v4/queries/qm\"",
                    "\"github.com/volatiletech/sqlboiler/v4/queries/qmhelper\"",
                    "\"github.com/volatiletech/strmangle\""
                ]
            },
            "test": {
                "Standard": [
                    "\"bytes\"",
                    "\"reflect\"",
                    "\"testing\""
                ],
                "ThirdParty": [
                    "\"github.com/volatiletech/sqlboiler/v4/boil\"",
                    "\"github.com/volatiletech/sqlboiler/v4/queries\"",
                    "\"github.com/volatiletech/randomize\"",
                    "\"github.com/volatiletech/strmangle\""
                ]
            },
            "singleton": {
                "boil_queries": {
                    "Standard": null,
                    "ThirdParty": [
                        "\"github.com/volatiletech/sqlboiler/v4/drivers\"",
                        "\"github.com/volatiletech/sqlboiler/v4/queries\"",
                        "\"github.com/volatiletech/sqlboiler/v4/queries/qm\""
                    ]
                },
                "boil_types": {
                    "Standard": [
                        "\"strconv\""
                    ],
                    "ThirdParty": [
                        "\"github.com/friendsofgo/errors\"",
                        "\"github.com/volatiletech/sqlboiler/v4/boil\"",
                        "\"github.com/volatiletech/strmangle\""
                    ]
                }
            },
            "test_singleton": {
                "boil_main_test": {
                    "Standard": [
                        "\"database/sql\"",
                        "\"flag\"",
                        "\"fmt\"",
                        "\"math/rand\"",
                        "\"os\"",
                        "\"path/filepath\"",
                        "\"strings\"",
                        "\"testing\"",
                        "\"time\""
                    ],
                    "ThirdParty": [
                        "\"github.com/spf13/viper\"",
                        "\"github.com/volatiletech/sqlboiler/v4/boil\""
                    ]
                },
                "boil_queries_test": {
                    "Standard": [
                        "\"bytes\"",
                        "\"fmt\"",
                        "\"io\"",
                        "\"io/ioutil\"",
                        "\"math/rand\"",
                        "\"regexp\""
                    ],
                    "ThirdParty": [
                        "\"github.com/volatiletech/sqlboiler/v4/boil\""
                    ]
                },
                "boil_suites_test": {
                    "Standard": [
                        "\"testing\""
                    ],
                    "ThirdParty": null
                }
            }
        },
        "aliases": {},
        "version": "4.4.0"
    },
    "driver_config": {
        "blacklist": null,
        "dbname": null,
        "host": null,
        "pass": null,
        "port": null,
        "sslmode": null,
        "user": null,
        "whitelist": null
    },
    "schema": "",
    "dialect": {
        "lq": 0,
        "rq": 0,
        "use_index_placeholders": false,
        "use_last_insert_id": false,
        "use_schema": false,
        "use_default_keyword": false,
        "use_auto_columns": false,
        "use_top_clause": false,
        "use_output_clause": false,
        "use_case_when_exists_clause": false
    },
    "tables": null,
    "templates": null
}

おう、driver_configが全部null。。。

Issuesとか調べてみると

PSQLで設定ファイル読まれません
まったく同じ現象!
この人はPSQLだけど。。
とりあえずお前の環境がおかしい言われてクローズされてます。
環境変数がどうのこうのとあるので、やっぱり変な設定ファイル読みに行っている可能性が高いということで、SQLBoilerのコードをちょっとみてみることに。。

結果、環境変数気をつけるべし

コードを追っかけた結果、原因がわかりました。
sqlboilerのmain.go,270行目(記事執筆時点です。)にこんなコードがあります。
これ、psqlとかmysqlとか書いてあるtomlのテーブルの値を読みに行って、コマンドに渡すところなのですが、ここで、環境変数もみています。

func allKeys(prefix string) []string {
        fmt.Printf("Call allKeys:" + prefix)

        keys := make(map[string]bool)

        prefix += "."

        for _, e := range os.Environ() {
                splits := strings.SplitN(e, "=", 2)
                key := strings.ReplaceAll(strings.ToLower(splits[0]), "_", ".")

                if strings.HasPrefix(key, prefix) {
                        keys[strings.ReplaceAll(key, prefix, "")] = true
                }
        }

        for _, key := range viper.AllKeys() {
                fmt.Fprintln(os.Stderr, "viper key:", key)
                if strings.HasPrefix(key, prefix) {
                        keys[strings.ReplaceAll(key, prefix, "")] = true
                }
        }

        keySlice := make([]string, 0, len(keys))
        for k := range keys {
                keySlice = append(keySlice, k)
        }
        return keySlice
}

僕は環境変数に、$MYSQLという変数がありまして、その値をどうやら読んでしまって、おかしくなっていたらしい。
とりあえずMYSQLという名前があるとここで引っかかるので、名前を変更して、実行したら、とおりました。。
結構ハマってしまった。

SQLBoiler使う時は環境変数名気をつけた方がいいですよという話でした。
殴り書きですみません。
Issueの人もPSQLという環境変数用意しちゃっていたみたいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go testで"go: cannot find main module; see 'go help modules'"が出てきたとき

Goのバージョンをアップデートした後にgo testを実行したら,

go: cannot find main module; see 'go help modules'

と出てきて,先に進まない

調べてみると,GO111MODULEという環境変数が問題らしい
この環境変数が'on'になっているのが問題だと書いてあるところが多いのだが,私の場合は何も入っていなかった

対処方法

GO111MODULEを'auto'と設定するとうまくいった

go1.13以降だと,go env コマンドに -w オプションを付けて環境変数を設定できる

go env -w GO111MODULE=auto

ok.

参考文献

Go 言語の環境変数管理
VSCodeでGo開発、”cannot find main module;”

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GO言語から初めてみました(^O^;)

とにかく右も左もわからず、がむしゃらに本を見ながら打ち込んでみます。プログラミング超初級の方の為の内容です。そして↓のコードはGO言語になります。

hello.go
package main

import "fmt"

func main() {
    fmt.Println("はじめまして!")
}

package(パッケージ)の中にmain(メイン)という関数?があるってことでいいのかな?

import(インポート)はプログラミングでライブラリ?を読み込むことを示すらしい。で、(””)このダブルクォーテーション内にあるfmt(エフエムティー)というのもパッケージの1つでフォーマット(書式)を思わせるよう、ターミナル?に内容を表示する為の関数?を定義しているようです。

func(ファンク)は関数?を示す語で、横にmain(メイン)を付けるのが決まりだそうです。

そしてfmtパッケージにあるPrintln(プリントライン)という関数?で(はじめまして!)の括弧内の内容を出力させます。


<関数>
とは、色々な処理をまとめた物でGO言語では1つの「データ型」になります。1度入力した物を皆でシェアしたり、他の場面で使ったり、2度書く必要が無い優れものです。関数を作ることを(関数を定義する)といいます。

<ターミナル>
とは、コンピュータに文字で指示を行う(コマンド)為のプログラムで、「端末」とも呼ばれます。macOSやLinuxなどのOSで利用され、Windowsでは「コマンドプロンプト」になります。

<ライブラリ>
とは、プログラム?の部品を集めたファイルのこと。

<プラグラム>
とは、コンピュータに対する命令(処理)を記述したもの。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 192 C - Kaprekar Number(Golang)

はじめに

SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192)の[C - Kaprekar Number]のメモです。

どのような問題だったか

  • 入力は、「N, K」(例:436, 2)
  • Nの各桁の数字を大きい順に並べる(例:436が与えられた場合、643にする)
  • Nの各桁の数字を小さい順に並べる(例:436が与えられた場合、346にする)
  • それぞれ並び替えたものを除算した結果を再度並び替えて除算とK回数繰り返した時の数を出力する(1回目643-346=297, 2回目972-279=693。出力は、「693」)

詳細は、C - Kaprekar Numberにてご確認ください。
主に求められるポイントは以下で、ほぼ言語仕様への理解が必要な問題です。

  • 入力された数値を各桁ごとに分解できること
  • 数字の降順ソートができること
  • 数字の昇順ソートができること
  • ソート結果は最終的に数値であること(計算ができること)

解答

2つ提出しました。

  • 提出コード①「入力された数値を各桁ごとに分解してソート」
  • 提出コード②「入力された数値を文字列に変換し、各桁ごとに分解してソートした後数値に戻す」

愚直に考えると①ですが、数学的知識(3桁の数字から各桁の数値を取り出す)が必要です。
②は文字列にすることで、stringsが使えるためint型より扱いが容易になります。コードも②の方がすっきりしました。
以下は、公式解説のコメントより

問題文に指示された通りの3つの関数を作成し、数列を順に計算していけばよいです。
g1及びg2の計算は多くの言語では「数値を文字列に変換してからソートし、数値に戻す」とすると容易です。

提出コード①

入力された数値を各桁ごとに分解してソートします。

package main

import (
  "fmt"
  "sort"
  "strconv"
)

func main() {
  var n, k int 
  fmt.Scan(&n, &k)

  a := n

  // k回f(x)を繰り返した残り
  for i:=0; i < k; i++ {
    a = f(a)
  }
  fmt.Println(a)  
}

// 数値→Slice
func intToSlice(n int) []int {
  n_sli := []int{}

  // 10で割ったあまり=1の位(723%10は3)
  for n > 0 {
    n_sli = append(n_sli, n%10)
    n /= 10
  }
  return n_sli
}

// Slice→数値
func sliceToInt(ns []int) int {  
  n_str := ""
  for _, v := range ns {
    n_str += strconv.Itoa(v)
  }
  n_int, _ := strconv.Atoi(n_str)
  return n_int
}
// 降順
func g1(x int) int {
  xx := intToSlice(x)
  sort.Sort(sort.Reverse(sort.IntSlice(xx)))
  return sliceToInt(xx)
}

// 昇順
func g2(x int) int {
  xx := intToSlice(x)
  sort.Ints(xx)
  return sliceToInt(xx)
}

func f(x int) int {
  return g1(x) - g2(x)
}

コード長:923 Byte
実行時間:188 ms
メモリ:6604 KB

所感

ソートの書き方が複雑すぎる

①②共にですが、降順ソートの書き方の難解さに圧倒されます。

sort.Sort(sort.Reverse(sort.IntSlice(xx)))

文字列と違い、容易に分割することができない

自前で数値→Slice(intToSlice)を用意しています。
ソートするためには、まずXX桁の数値をXX個の要素にする必要がありますので、Sortが使えるスライスに変化します。
コメントに記載していますが、以下の方法により1桁ごとの値を取得できます。

入力例)732
 1の位、732÷10=73あまり2                →あまりをスライス[0]に格納
 先の計算の解を使って10の位、73÷10=7あまり3 →あまりをスライス[1]に格納
 先の計算の解を使って100の位、7÷10=0あまり7 →あまりをスライス[2]に格納
 先の計算の解が0になったら終了
 出力、int[2, 3, 7]
結合も容易にできない

自前でSlice→数値(sliceToInt)を用意しています。
最終的に除算するので、ソートされたスライスをint型にする必要がありますが、
単純に要素を足していくと数値型は、2+3+7=12となってしまいます。
そのため、一度文字列にして"237"を作成した後、文字列→数値に変換しています。

提出コード②

入力された数値を文字列に変換し、各桁ごとに分解してソートした後数値に戻す
なお、このやり方は一般的ですが、文字列での比較となるので、[2,11,8]など桁数が異なるものが与えられている場合、昇順[2,8,11]としたいところが昇順[11,2,8]となってしまうので使えません。

package main

import (
  "fmt"
  "sort"
  "strconv"
  "strings"
)

func main() {
  var n, k int 
  fmt.Scan(&n, &k)

  a := n
  // k回f(x)を繰り返した残り
  for i:=0; i < k; i++ {
    a = f(a)
  }
  fmt.Println(a)
}
// 降順
func g1(x int) int {
  n_sli := strings.Split(strconv.Itoa(x), "") // 文字列→Slice
  sort.Sort(sort.Reverse(sort.StringSlice(n_sli)))
  n_int, _ := strconv.Atoi(strings.Join(n_sli, "")) // Slice→数値(文字列)
  return n_int
}

// 昇順
func g2(x int) int {
  n_sli := strings.Split(strconv.Itoa(x), "") // 文字列→Slice
  sort.Strings(n_sli)
  n_int, _ := strconv.Atoi(strings.Join(n_sli, "")) // Slice→数値(文字列)
  return n_int
}

func f(x int) int {
  return g1(x) - g2(x)
}

コード長:786 Byte
実行時間:167 ms
メモリ:6592 KB

所感

ソートの書き方は数値型か文字型かでメソッド名は異なるが、基本変わらず
昇順ソート
sort.Ints([]int) //int型スライス
sort.Strings([]string) //string型スライス
文字列→スライス(string型→[]string型)が容易
②から抜粋
n_sli := strings.Split(strconv.Itoa(x), "")

strconv.Itoa(int)で、入力数値→文字列の変換をしています。
変換したらstrings.Split(string, "")が使えるので、区切り文字なし(1桁ごと)でスライスできます。
※数値にこのようなメソッドはないようです。

スライス→文字列([]string型→string型)が容易
②から抜粋
n_int, _ := strconv.Atoi(strings.Join(n_sli, ""))

strings.Join([]string, "")を使うと、スライスの要素を区切り文字なし(1桁ごと)で文字列にできます。
※Split同様、数値にこのようなメソッドはないようです。
最後に計算のため、strconv.Atoi(string)で、文字列→数値の変換をしています。

おわりに

並び順をどうにかする問題、毎回ぐるぐる考えてしまうのでどうにかしたいです。

追記

コメントでいただいた[]stringではなく[]byteでソートするやり方が一番きれいではやい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

slackの操作をシェルプロっぽくしてChatOps捗りツール作りました!

まえせつ

slack使ってるとネイティブアプリがWebアプリぽい操作感なのに、ターミナルがシェルなのでモヤる時ありませんか?

  • メッセージを再編集するのに↑キーで履歴を選びたい。ネイティブアプリの実装は何か違う感が・・
  • 特定の人へのメンションを多用するので毎度コピペするのが面倒。@漏れで読んでくれない、とか。
  • 同じ入力をするのにエイリアスみたいなので入力を減らしたい。ファイルサーバーの途中までのパスとか。

特にslack→サーバーへコマンド投げ込みボットが居る場合はとっても面倒になります。

ボットを呼んでコマンド投げて、メンション入れて確認依頼して・・次のコマンド入れて・・をコピペして繰り返すことになります。

というのをUXを解消してくれるのが、このツール!

作ったわ!

詳しくはリポジトリで。バイナリをダウンロードして即使いたいならこっち

zlib1.dllが無い場合のエラー

使用しているrobotgoが依存しているのでzlib1.dllが無い場合、エラー落ちが予測されます。

こちらのリンクからダウンロードしてパスが通ってる場所に配置すると動かせます

つかいかたは?

実行ファイルを起動すればプロンプトが出ます。そこに入力した内容をslackのネイティブアプリに投げ込んでくれます。

1.gif

一行モードにすれば書き込みを待たずに一行単位で投げ込んでくれるのでサクサクChatOps!!

2.gif

あとがき

三か月ぶりの個人開発!!
・・・三か月でさび付いてくるもんなんですね。過去に自分書いたの読めないわ、書けないわ。w
ただ、また思い付きで何か作ってみようってモチベが出てくるのは良い事だわ。

続いて、チャットボット側も作っていきたいな。
チームからChatから依頼受けたら、わざわざターミナル上げて、ログインしてログ見て、コピペしてみたいな繰り返しをやめて
チャットからコマンド流して共有して、オペレーション履歴も残って・・みたいな世界観でSREっていきたい!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiita/Zennの投稿をGitHubプロフィールに自動反映するためのツールを作った

この記事は Feed で GitHub の README.md に反映されるはず。という実験用のエントリです。

記事はこちら:https://zenn.dev/ikawaha/articles/20210221-c8f2d9ac028ae49d551a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go の chan のことをよく知らなかったので試してみた

go の chan のことをよく知らなかったので試してみた

知っておくといいかもしれないこと。

go の chan が close済みかどうかを知る方法は、昔あったらしいけど今はない。
途中のバージョンで削除されたらしい。

削除された理由はおそらく、無いほうがいいから。あるとむしろトラブルの原因になる。
「close済みでなければ xxx する」というコードを書いても、「close済みでなければ」と「xxxする」の間で他人が close する可能性があり、それを排除することができない。

chan が nil だったり close済みだったりする場合の動作

nil の chan からの読み出しと書き込み。

そこで止まり、書き込み・読み出しが終わることはない。
それが唯一の goルーチンの場合、panic する。Recover できない。

close済み の chan への書き込み

panic する。Recover できる。
send on closed channel というメッセージ。
型は runtime.plainError

close済み の chan からの読み出し

panic せず、読み取れる。

読むべき値がない場合

ch := make(chan [3]int)
close(ch)
log.Println(<-ch, "ok") //=> [0 0 0] ok
log.Println(<-ch, "ok") //=> [0 0 0] ok
log.Println(<-ch, "ok") //=> [0 0 0] ok

ゼロ値が取れる。

close 前に値を積んである場合

ch := make(chan int, 2)
ch <- 111
ch <- 222
close(ch)
log.Println(<-ch, "ok") //=> 111 ok
log.Println(<-ch, "ok") //=> 222 ok
log.Println(<-ch, "ok") //=> 0 ok
log.Println(<-ch, "ok") //=> 0 ok

close 前に積んだ値が読める。
積んだ値がなくなったらゼロ値になる。

nil の chan を含む select

nil である chan から読み出しを試みるとそこで止まってしまうが、 select の場合は止まらずに「読めなかったね、じゃあ次」となるので問題ない。

chI := make(chan int)
var chF chan float64 = nil
go func() {
    time.Sleep(500 * time.Millisecond)
    chI <- 123
}()
select {
case i := <-chI:
    log.Println("i:", i) // ここに来る。
case f := <-chF:
    log.Println("f", f) // ここには来ない。
}

上記の例では、500ms 後に chI から 123 が読み出され、特に困ったことは何も起きない。

close 済み chan を含む select

close 済みの chan から読み込むとゼロ値が取れるので、以下の例では即座に close 済みの chF から 0.0 が読まれる。
500ms 後に chI に積まれる 123 は、このコードでは読まれない。

chI := make(chan int)
chF := make(chan float64)
close(chF)
go func() {
    time.Sleep(500 * time.Millisecond)
    chI <- 123
}()
select {
case i := <-chI:
    log.Println("i:", i) // ここには来ない。※
case f := <-chF:
    log.Println("f", f) // ここに来る。
}

※ select を含む go ルーチンが何らかの事情で goルーチン作成から select 到達までに 500ms 以上の時間を要していたら chI の方に来るかもしれない。

nil である chan の range

nil である chan を range にわたすと、そこで処理が止まる。

var ch chan int = nil
for i := range ch {
    log.Println(i)
}
log.Println("exit for")

ループ内に入ることもないし、ループを抜けることもない。
このループが唯一の go ルーチンである場合、recover 不能な panic が発生する。

close 済み chan の range

当たり前だけど、 for..range のループが始まってから close したのと同じような状況になる。

ch := make(chan int)
close(ch)
for i := range ch {
    log.Println(i) // ここには来ない
}
log.Println("exit for") // すぐにここに来る

つまり、ループには入らず、すぐにループを抜ける。

nil である chan と close 済み chan の close

両方とも panic になる。
型は runtime.plainError
メッセージは「close of nil channel」と「close of closed channel」。

まとめ

やること nil である chan close 済み chan
読み出し そこで停止 ゼロ値が得られる
書き込み そこで停止 パニック
selectcase その case にはならない ゼロ値が得られる
range そこで停止 ループに内入らずにループ終了
close panic panic

こうしてみると、 nil である chan は、ほぼ、書き込み側からは「誰も読んでくれない バッファのない chan」、読み出し側からは「誰も書いてくれない chan」として機能しているが、 close の振る舞いだけが異なる。

chan の nil チェックを含むパターン

フィールドの nil チェック後に read / write

こんなの。

type hoge struct {
    ch chan struct{}
}

func (h *hoge) foo() {
    if h.ch == nil {
        return
    }
    // [A]
    a := <-h.ch // [B]
    doSomething(a)
}

これはダメ。 [A] の箇所で誰かが h.chnil にするかもしれない。nil になると [B] で止まる。
上記の例は read だけど、write も同じ理由でダメ。

ローカル変数の nil チェック後に read

こんなの。

func (h *hoge) bar() {
    ch := h.ch
    if ch == nil {
        return
    }
    // [A]
    a := <-ch // [B]
    doSomething(a)
}

これは、明らかに駄目ということではないが、わりとよろしくない感じ。
[A] で h.ch を誰かが nil にしても、 ch にはその前の値が入っている。
ただ

  • ch はすでに close 済みで、[B] で読める値はゼロ値かもしれない。

ということで、わりとまずいことになりそうではある。

ローカル変数の nil チェック後に write

こんなの。

func (h *hoge) baz() {
    ch := h.ch
    if ch == nil {
        return
    }
    ch <- struct{}{} // [B]
}

[B] がnil への write にならないことは間違いないが、 ch が close 済みだとパニックになる。

つまり、事前条件と結果は

  • h.ch が close 済みではない → ch になんか送る
  • h.ch が close 済み → panic
  • h.chnil → なにもしない

ということになる。
read の例と同じく、[B] の時点では h.chnil かもしれないので、 ch を read する人はもういないかもしれない。

というわけで

というわけで、 chan が nil かどうかで動作を変えるのはわりとうまく行かない感じ。
「close したら nil にして、nil かどうかで条件分岐」とかいう作戦より「そもそも close かどうかが不確定な状況にならないようにする」という作戦を取るべきと思う。

read 側が range で回せば write 側の close でループ抜けるので、それで済むならそれがよい。
あるいは、一回しか write しなくて、書き込み側は write → close。読み出し側は read 一回とか。

chan の仕様がそのようなメッセージとなっていると思う。

複雑なケースの場合に安全なパターンを作るのが難しくなることもあると思うけど、 nil チェックでは安全にしにくい。

あと。close 済みの chan を read するとゼロ値が得られるのが気持ち悪いような便利なような。
たとえば、 chan bool にしておいて、いつも ch<-true としておけば、 false が得られた場合に close 済みだということを知ることができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バッファって何?

はじめに

GoのChannelに関して学んでいる最中に,バッファという単語が出てきました.
聞いたことはあるけど,理解はできていない?
いい機会なので,調べてみました!

簡単に

データをやりとりするときの一時的な記憶領域
のこと.

きちんと

英語ではBufferと書いて,「緩衝材」のような意味を持つようです.
コンピュータの「一時的な記憶領域」のことを指していて,それが転じてビジネスでは「ゆとり」などの意味合いで使われるようになったらしです?

GoのChannelはデータを保持するためのデータ構造で,容量を指定できます.
これがバッファです.
cap関数を使うことで,見ることができます.

main.go
channel := make(chan int, 5)
fmt.Println(cap(channel))
terminal
0

と出力されました.
このmake関数で,第2引数に指定された 5 がバッファサイズです.
今は何も受信していないため,容量は 0 と表示されました.

ではこの後に,以下のように送信すると,

main.go
channel <- 1
channel <- 2
channel <- 3
channel <- 4
channel <- 5
channel <- 6
fmt.Println(cap(channel))
terminal
fatal error: all goroutines are asleep - deadlock!

と出力されました.
この deadlock は保存領域が足りないよと言うことで,
バッファのサイズが足りないと怒られてしまったのですね?

Channelはqueueの性質を持っていで,バッファはqueueのサイズともいえます.
queueは先入れ先出しの性質を持っているので,

errorを解消するには,受信をしてしまえばいいのではないでしょうか.

main.go
channel <- 1
channel <- 2
channel <- 3
channel <- 4
fmt.Println(channel<-)
channel <- 5
channel <- 6
fmt.Println(cap(channel))
terminal
1
5

こんな感じで出力されました!
この 1 は先入された1ですね?
5 は保存されている容量だと思います.

まとめ

自分の手で動かしてみて,バッファは
データをやりとりするときの一時的な記憶領域
と言うことが理解できたような気がします?‍♂️

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Golang] 構造体埋め込み時のバリデーションについて

GolangのORMライブラリGORMで、モデルの関連づけを表現した際のバリデーションにつまづいたのでメモ。

使用ライブラリ群

困ったこと

GORMで以下のようなHasOne関係のモデルを作り、それぞれの構造体にvalidateタグを付与している状況。

type User struct {
    ID        int       `json:"id" gorm:"primary_key"`
    Email     string    `json:"email" validate:"email,required,max=255"`
    Password  string    `json:"password" validate:"required,min=8,max=16"`
    Profile   Profile   `json:"profile" gorm:"foreignKey:ID"`
}

type Profile struct {
    ID        int       `json:"id" gorm:"primary_key"`
    UserID    int       `json:"userId" validate:"required"`
    Name      string    `json:"name" validate:"required,max=255"`
    Hobby     string    `json:"hobby"`
}

UserのみのCreate(Profileは作らない。Save前にValidation実行)をするAPIを用意し、以下のパラメーターをPOSTすると、

{
    "email": "hoge@exa.com",
    "password": "hogehoge"
}

以下のようなバリデーションエラーとなる。

Key: 'User.Profile.UserID' Error:Field validation for 'UserID' failed on the 'required' tag
Key: 'User.Profile.Name' Error:Field validation for 'Name' failed on the 'required' tag

原因は単純で、Userに埋め込んでるProfileのvalidateタグも見にいってるので、バリデーションエラーとなる。困った。

解決策

validatorのリファレンスを探ってると、Skip Fieldなるものを発見。

validate:"-"で検証をスキップできるみたい。User.Profileに追記する。

type User struct {
    ID        int       `json:"id" gorm:"primary_key"`
    Email     string    `json:"email" validate:"email,required,max=255"`
    Password  string    `json:"password" validate:"required,min=8,max=16"`
    Profile   Profile   `json:"profile" gorm:"foreignKey:ID" validate:"-"` // ココに追記した
}
...

Profileの検証がスキップされてUserの作成に成功。

気付き

  • jsonタグでも"-"(ハイフン)でフィールドが無視できたと思うので、struct tagsのvalueにおける"-"は無視する的な使い方をするっぽい?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む