zsh の配列の index は1から始まる

夏ですね

地下鉄の駅で電車待ってるとき、ついホームのところどころにあるエアコンの前に立っちゃいますよね!
あと、弱冷房車はあるのになんで強冷房車はないんだって時々考えてしまいますよね!

本題

というわけでまずは以下の実行結果を。

$ bash
$ ar=(Apple Banana Coconut Durian)
$ echo ${ar[1]}
Banana
$ zsh
$ ar=(Apple Banana Coconut Durian) 
$ echo ${ar[1]}
Apple

配列 ar にいくつかの要素を格納して、その index = 1 の要素を取り出すと、bashzsh で取れる要素が異なるという(汗
zshのindexは1から始まるようだ。

ところでなんで他の多くのshellが配列の添字を0から始めているのにzshは1から始めているんだろうか。
いろいろ調べてみたところ、どうやら csh(同じく1から始まる) の影響なのではないか、と。

Re: zero- vs one- based array indexing?

> Can someone give me a quick history of why zsh defaults to one-based
> array indexing? Bash and ksh appear to use zero-based indexing, and
> probably most CS types prefer that too.

Basically from csh, I think. Arrays were a bit of an afterthought in
earlier Bourne-style shells, so zsh didn't take much notice.

1から始めるようにしたことについてそんなに深く考えたわけではなさそうだ、とのこと。
実際のところどうなんだろう。

ちなみに debian で採用されている dash では、高速軽量を目的としているからなのか、そもそも配列は使えない。

set -u : 未定義の変数を使用しようとしたらエラーにする

typoなんてのは普段よくあることで、そいつに無駄な時間をとられてしまうことが日常茶飯事だ。

#!/usr/bin/env bash

MY_NAME="Miku"
MY_BIRTHDAY="10/19"
MY_HOBBY="Dance"

echo $MY_NAME
echo $MY_BIRTDAY
echo $MY_HOBBY

上記の出力結果はどうなるだろうか。上から"Miku" "10/19" "Dance"と表示されそうなものだが、実際はこうなる。

Miku

Dance

MY_BIRTHDAYが出力されていないが、これは変数名を入力し間違えているから。
こういうtypoに気付きやすくするために、set -u を指定すると良い。

#!/usr/bin/env bash

set -u

MY_NAME="Miku"
MY_BIRTHDAY="10/19"
MY_HOBBY="Dance"

echo $MY_NAME
echo $MY_BIRTDAY
echo $MY_HOBBY

上記を実行すると、以下のようなエラーとなり、該当箇所で変数を入力し間違えていることに簡単に気づける。

Miku
miku.sh: line 10: MY_BIRTDAY: unbound variable

ただ、定義済みと思い込んでいた変数をうっかり使ってしまい上記のエラーに遭遇する場合も多々ある。例えば

#!/usr/bin/env bash

set -u

PERLLIB=$PERLLIB:$HOME/mylib

...

PERLLIB は Perl においてモジュールパスを指定する環境変数だが、常に定義済みとは限らず、Perl を普段から使っている人だと未定義の場合でもつい上記のように使ってしまいエラーに遭遇しやすいかもしれない(*1)。その場合は問題の箇所のみ set -u の宣言前に移動するとよい。

(*1) 昨日のオイラ

Many-to-many関係を使う

今回は GORM で Many-to-many 関係を扱う。

複数のユーザと複数の楽曲が用意されていて、各ユーザのお気に入りの楽曲を格納できるようにする。E-R図にするとこんな感じ。

f:id:taegawa:20170114140153p:plain

とりあえずコードはこんな感じ。

package main

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

const (
	dsn = "root@/association?parseTime=true&loc=Asia%2fTokyo"
)

type User struct {
	gorm.Model
	Name      string
	FavMusics []Music `gorm:"many2many:user_fav_musics;"`
}

type Music struct {
	gorm.Model
	Title string
}

func main() {
	db := prepare()
	defer db.Close()

	for _, id := range []uint{1, 2, 3} {
		var user User
		db.First(&user, id).Related(&user.FavMusics, "FavMusics")
		fmt.Println("---------------------------")
		fmt.Printf("%s さんのお気に入り曲\n", user.Name)
		for _, music := range user.FavMusics {
			fmt.Printf("曲名: %s\n", music.Title)
		}
	}
	fmt.Println("---------------------------")
}

func prepare() *gorm.DB {
	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		panic("Failed to connect database")
	}
	//db.LogMode(true)
	db.DropTableIfExists(&User{}, &Music{}, "user_fav_musics")

	db.AutoMigrate(&User{})
	db.AutoMigrate(&Music{})

	prepareUsers(db)
	prepareMusics(db)
	prepareFavMusics(db)

	return db
}

func prepareUsers(db *gorm.DB) {
	var usernames = []string{"Mario", "Luigi", "Peach"}

	for _, username := range usernames {
		user := User{
			Name: username,
		}
		db.Create(&user)
	}
}

func prepareMusics(db *gorm.DB) {
	var titles = []string{
		"放課後ロマンス",
		"私Loveなオトメ",
		"ニーハイエゴイスト",
		"禁断無敵のダーリン",
		"S・M・L",
	}

	for _, title := range titles {
		music := Music{
			Title: title,
		}
		db.Create(&music)
	}
}

func prepareFavMusics(db *gorm.DB) {
	//  お気に入り楽曲
	//  {user.ID, music.ID} の組
	var favorites = [][]uint{
		{1, 1}, {1, 2}, {1, 4},
		{2, 2}, {2, 3}, {2, 5},
		{3, 2}, {3, 5},
	}
	for _, favorite := range favorites {
		var user User
		user.ID = favorite[0]

		var music Music
		music.ID = favorite[1]

		db.Model(&user).Association("FavMusics").Append(&music)
	}
}

(実行結果)

---------------------------
Mario さんのお気に入り曲
曲名: 放課後ロマンス
曲名: 私Loveなオトメ
曲名: 禁断無敵のダーリン
---------------------------
Luigi さんのお気に入り曲
曲名: 私Loveなオトメ
曲名: ニーハイエゴイスト
曲名: S・M・L
---------------------------
Peach さんのお気に入り曲
曲名: 私Loveなオトメ
曲名: S・M・L
---------------------------

Model の定義

type User struct {
	gorm.Model
	Name      string
	FavMusics []Music `gorm:"many2many:user_fav_musics;"`
}

type Music struct {
	gorm.Model
	Title string
}

モデルはユーザ(User)と楽曲(Music)のみ定義する。User 側に FavMusics というフィールドを追加し、ここにお気に入りの楽曲が格納されるようにする。また FavMusics フィールドのタグで、many-to-many 関係を使用すること、および関係テーブルの名称を上記のように宣言する。AutoMigration 使用時は GORM が関係テーブル user_fav_musics を自動的に作成してくれる。

検索

		db.First(&user, id).Related(&user.FavMusics, "FavMusics")

特定ユーザに紐づくお気に入り楽曲を取得する場合は、Related()の第2引数でフィールド名を指定する。LogMode を true にすれば、上記を実行したとき以下のようなSQL文が発行されているのが分かる。

User 検索

SELECT * 
  FROM `users`  
 WHERE `users`.deleted_at IS NULL 
    AND ((`users`.`id` = '3')) 
 ORDER BY `users`.`id` ASC LIMIT 1

User のお気に入り楽曲を検索

SELECT `musics`.* 
  FROM `musics` 
    INNER JOIN `user_fav_musics` ON `user_fav_musics`.`music_id` = `musics`.`id` 
 WHERE `musics`.deleted_at IS NULL 
    AND ((`user_fav_musics`.`user_id` IN ('3'))) 
 ORDER BY `musics`.`id` ASC

関係の追加

		var user User
		user.ID = favorite[0]

		var music Music
		music.ID = favorite[1]

		db.Model(&user).Association("FavMusics").Append(&music)

多分 Association Mode を使う方法が一番素直なのではないかと。Association() の引数で関係を定義したフィールド名を指定し、Append()で追加する。ちなみに user, music は、両者の関連を特定するために必要なフィールド(ここではID)だけ定義されていれば十分。あらかじめすべてのフィールドをDBから問い合わせておく必要はない。

INSERT INTO `user_fav_musics` (`music_id`,`user_id`) 
SELECT '1','1' 
  FROM DUAL 
 WHERE NOT EXISTS 
    (SELECT * FROM `user_fav_musics` WHERE `music_id` = '1' AND `user_id` = '1')

発行されるSQL文は上記の通り。同じ組み合わせがすでに存在する場合、二重に登録されることはないようだ。

Association で任意の外部キー名を使う

初詣行ってきました

大吉引いて幸先良いです。
あと、巫女さんが可愛かったです。

本題

GORM の Association 関連で、任意の外部キー名を使用しようとしてハマったのでメモ。

標準の外部キー名を使う方法

その前に、まずは Convention に従った外部キー名を使う方法。
これはGORMのマニュアルにも書かれている通り。今回は Belongs-to の関連で試してみる。

f:id:taegawa:20170108063437p:plain

今回は上記の通り、ユーザ(users)と、それに紐づくアイコンイメージ(images)をデータとして保持できるようにする。アイコンイメージには任意のイメージ名と、そのURLを持っている。

※以下の例では association という database を使用する。もともと同名の database があって、かつ users, images というテーブルがあると一旦削除されてしまうので注意。

package main

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

const (
	dsn = "root@/association?parseTime=true&loc=Asia%2fTokyo"
)

type User struct {
	gorm.Model
	Name    string
	Image   Image
	ImageID uint
}

type Image struct {
	gorm.Model
	Name string
	Url  string
}

func main() {
	db := prepare()
	defer db.Close()

	for _, id := range []uint{1, 2, 3} {
		var user User
		fmt.Println("-------------------------------")
		db.First(&user, id).Related(&user.Image)
		fmt.Printf("名前    : %s\n", user.Name)
		fmt.Printf("アイコン:\n")
		fmt.Printf("  名称: %s\n", user.Image.Name)
		fmt.Printf("  URL : %s\n", user.Image.Url)
	}
	fmt.Println("-------------------------------")
}

func prepare() *gorm.DB {
	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		panic("Failed to connect database")
	}
	db.DropTableIfExists(&User{}, &Image{})

	db.AutoMigrate(&Image{})
	db.AutoMigrate(&User{})

	var members = []map[string]string{
		{"Name": "ミク", "ImageFile": "miku.jpg"},
		{"Name": "マホ", "ImageFile": "maho.jpg"},
		{"Name": "コヒメ", "ImageFile": "kohime.jpg"},
	}

	for _, member := range members {
		image := Image{
			Name: member["Name"] + "アイコン",
			Url:  "https://image.example.com/" + member["ImageFile"],
		}
		db.Create(&image)

		user := User{
			Name:    member["Name"],
			ImageID: image.ID,
		}
		db.Create(&user)
	}

	return db
}

実行結果

-------------------------------
名前    : ミク
アイコン:
  名称: ミクアイコン
  URL : https://image.example.com/miku.jpg
-------------------------------
名前    : マホ
アイコン:
  名称: マホアイコン
  URL : https://image.example.com/maho.jpg
-------------------------------
名前    : コヒメ
アイコン:
  名称: コヒメアイコン
  URL : https://image.example.com/kohime.jpg
-------------------------------

Userのモデルは以下のように定義されている。

type User struct {
	gorm.Model
	Name    string
	Image   Image
	ImageID uint
}

ここでは、フィールド Image が Image型であることが宣言されているが、その取得元レコードへの外部キーが ImageID であることは特に明示していない。また、問い合わせで

		db.First(&user, id).Related(&user.Image)

としているが、Related()で関連レコードを取得する際にも、外部キーが ImageID であることを明示していない。

それでも Related() が問題なく外部キーを特定し関連レコードを取得できるのは、特に指示のない限り、

  1. 関連モデルの名称 + 'Id'
  2. 自身のモデルの名称 + 'Id'

を外部キーとみなすようになっているから。
ここでは Image が Image型であるため、

  1. ImageID
  2. UserID

の順に外部キーを探していき、同名のキーが見つかった時点でそれを使用する。

任意の外部キーを使う方法

ところが、外部キーの名称を (モデル名) + 'Id' 「以外」にしたいケースというのも多々ある。例えば今回はアイコンイメージのIDを ImageID というキー名で参照しているが、これはあまり良い名称とは言えない。アイコンイメージであるなら分かりやすく IconImageID としたほうがいいし、また背景画像など、同じ Image モデルを参照する別のフィールドが追加されたときは、少なくともどちらかは ImageID 以外の外部キー名を使わなければならない。

そこで User モデルの ImageID を IconImageID に変更してみる。

type User struct {
	gorm.Model
	Name        string
	IconImage   Image
	IconImageID uint
}

名称変更自体はこれで良いのだが、IconImageID というキー名はこのままでは外部キーとして認識されない。これを解決する方法はいくつかあるが、GORM ドキュメントの Association の項で触れられている範囲内では2つのやり方がある。

Related() で明示的に外部キーを指定する方法

Related() の第2引数で外部キー名を明示的に指定することができる。

        db.First(&user, id).Related(&user.IconImage, "IconImageID")

お手軽。

Association Mode を使う

モデルを宣言する際、モデル間の関連についてタグで指定することができる。

type User struct {
	gorm.Model
	Name        string
	IconImage   Image `gorm:"ForeignKey:IconImageID"`
	IconImageID uint
}

上記のようにすると、「IconImage でレコードを取得する際は、外部キーとして IconImageID を使用する」と明示的に指示できる。
そして関連レコードを取得する際は、

        db.First(&user, id).Association("IconImage").Find(&user.IconImage)

のように、Association で取得対象のフィールド名を指定すれば、タグで指定したものが外部キーとして使用される。

※修正済みのソースはこちら

Type Switch

interface{}型で宣言された変数の値の具体的な型により処理を分岐させたい、というケースが考えられる。Type Switch という仕組みでそれを実現できる。switch - case 文を使って、型ごとの処理内容を case 内に記述する。

公式ドキュメントの Effective Go にもきちんと記載されているのだが、初めて見たとき少し戸惑うので一応メモ。


例えば、ある値に「かけ算」をする以下のような関数を作るとする。

  • 数値(int)が渡された場合は、その数に指定された数を掛けた結果を返す。
  • 文字列(string)が渡された場合は、それを指定された回数だけ繰り返す。

ソースはこんな感じになる。(簡略化するため int, string 以外では nil を返すものとする)

package main

import (
	"fmt"
	"strings"
)

func multiple(val interface{}, times uint) interface{} {
	fmt.Printf("Type of val is %T\n", val)

	switch val := val.(type) {
	case int:
		return val * int(times)
	case string:
		return strings.Repeat(val, int(times))
	default:
		fmt.Printf("[WARN] Can't handle this type of value : %T\n", val)
		return nil
	}
}

func main() {
	fmt.Printf("3 * 5 = %v\n\n", multiple(3, 5))
	fmt.Printf("'逃げちゃダメだ' * 5 = %v\n\n", multiple("逃げちゃダメだ", 5))
	fmt.Printf("1.5 * 5 = %v\n\n", multiple(1.5, 5))
}

実行結果

Type of val is int
3 * 5 = 15

Type of val is string
'逃げちゃダメだ' * 5 = 逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ逃げちゃダメだ

Type of val is float64
[WARN] Can't handle this type of value : float64
1.5 * 5 = <nil>


ポイントを一つずつ見ていくと、

func multiple(val interface{}, times uint) interface{} {

まず、引数 val (掛けられる数)を interface{} 型と宣言し、任意の型の値を受け取れるようにする。
引数 times は、掛ける数。

    switch val := val.(type) {

val.(type) というのが一見奇妙に見えるが、これは普通のキャスト。int 型にキャストするために val.(int) とするのと同じ。
これにより、interface{}型として扱われていた val が、本来の型で再代入されることになる。val の本来の型が int であるなら、 val := val.(type) によりはじめて int 型として扱われる。
(補足も参照)

なおかつ switch 文の式としてこのように書くことにより、 interface{} 型として受け取った val の本来の型が何かを判定し、case で分岐させることができる。


(補足) 以下は自分でよく理解できていないところ。

キャストを行わないと、例えば

        //  val := val.(type) を以下のように変更
	switch val.(type) {

のようにすると、val が interface{} 型のままではかけ算に使えないといったコンパイルエラーとなる。

# command-line-arguments
./multiple.go:11: invalid operation: val * int(times) (mismatched types interface {} and int)
./multiple.go:13: cannot use val (type interface {}) as type string in argument to strings.Repeat: need type assertion

ところが、結果の Type of val is ... からも分かる通り、val は別に interface{} 型ではなく、引数として受け取った時点で具体的な型を持っている。
だったらキャストは不要なんじゃないかと思うのだが、コンパイル時にそれを保証できないということなのかなぁ。
そこはちとモヤモヤしてる。

あけましておめでとうございます

今年はちゃんと記事書いていきたいと思います。

#何年か前にも同じこと言った気がする←


一応、今年は目標を2つ立てました。
1つは割と近々で実現したいこと。もう一つは2017年になんとか実現したいこと。
頑張るよー。

GORMを使ってみた

goで使えるO/R Mapperは結構いろいろあるみたいで、とりあえず今日1日 gorpとGORMを使ってみた。

gorp
GitHub - go-gorp/gorp: Go Relational Persistence - an ORM-ish library for Go

GORM
Getting Started with GORM · GORM Guide

gorpはシンプルに使える、GORMは多機能、という印象。とりあえず今回はGORMについてまとめてみる。

ソースは以下に置いてあります。
hatena_blog/go/gorm at master · egawata/hatena_blog · GitHub

下準備

mysql サーバを立て、以下の準備を行う。

databaseの作成

mydb2 という名称のデータベースを作成。

CREATE DATABASE mydb2 DEFAULT CHARSET=utf8;

table の作成

GORMには関係テーブルを便利に扱う機能があるため、今回は2つテーブルを作成し関係を持たせるようにした。1人のMemberが、複数の Hobby を持つようにしてある。

CREATE TABLE member (
    id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    birthday VARCHAR(5),
    blood_type CHAR(2),
    created_at DATETIME,
    updated_at DATETIME,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

CREATE TABLE hobby (
    id INTEGER NOT NULL AUTO_INCREMENT,
    member_id INTEGER NOT NULL,
    name VARCHAR(100) NOT NULL,
    created_at DATETIME,
    updated_at DATETIME,
    PRIMARY KEY (id),
    CONSTRAINT FOREIGN KEY (member_id) REFERENCES member(id)
) ENGINE=InnoDB DEFAULT CHARSET=UTF8;

ユーザの作成

user1 というユーザを作成した。

GRANT ALL ON mydb2.* TO user1@'localhost' IDENTIFIED BY 'password1';

コーディング

必要なパッケージの import

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

mysqlから使用する場合、最低限以上の2つが必要。

データを格納する構造体の作成

type Member struct {
	Id        int64 `gorm:"primary_key"`
	Name      string
	Birthday  string
	BloodType string
	Hobbies   []Hobby
	CreatedAt time.Time
	UpdatedAt time.Time
}

type Hobby struct {
	Id        int64 `gorm:"primary_key"`
	MemberId  int64
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
}
テーブル/カラム名と構造体メンバ名の関連

上記のような構造体を作成すると、DB上のテーブル、カラムと構造体との関連付けが自動的に行われる。デフォルトでは

  • テーブル名 = 構造体の型名の複数形 (例:Member -> members)
  • カラム名 = 構造体の中の各メンバ名をsnake_caseにしたもの (例: BloodType -> blood_type)

デフォルトの命名規則を変えることも可能。例えば今回はテーブル名についてはデフォルトと異なり複数形にしていないので、以下のように実際のテーブル名を返すメソッドを追加する。

func (m *Member) TableName() string {
	return "member"
}

func (h *Hobby) TableName() string {
	return "hobby"
}

カラム名を変更したい場合は、構造体のメンバ宣言に以下のようにタグをつける。(今回は使用しないけど)

    Name string `gorm:"column:another_name"`
Has-Many 関係の表現

上記の Member 構造体の中には、元のmember テーブルにはない Hobbies というメンバが定義されている。

	Hobbies   []Hobby

これは、1人のMemberが複数の Hobby を持つというテーブル間の関係を表現している。このあたりも GORM がいい感じに処理してくれる。

追加・更新・削除日時の自動更新

CreatedAt, UpdatedAt, DeleteAt という time.Time 型のメンバを追加すると、GORM側でここに追加・更新・削除日時を自動的に入るようになる。

DB接続

const (
	dsn = "user1:password1@tcp(127.0.0.1:3306)/mydb2?parseTime=true&loc=Asia%2FTokyo"
)

        db, err := gorm.Open("mysql", dsn)
	defer db.Close()

parseTime=true を入れておかないと、time.Time型のメンバに値を取り込む際にエラーとなってしまう。またデフォルトではタイムゾーン世界標準時となるので、指定したいのなら loc=Asia/Tokyo のようにする(URIエスケープが必要)。

レコード追加

	members := []Member{
		{Name: "ミク", Birthday: "10/19", BloodType: "AB", Hobbies: []Hobby{{Name: "ブログ"}, {Name: "ショッピング"}}},
		{Name: "マホ", Birthday: "1/8", BloodType: "AB", Hobbies: []Hobby{{Name: "漫画"}, {Name: "ゲーム"}}},
		{Name: "コヒメ", Birthday: "11/24", BloodType: "O", Hobbies: []Hobby{{Name: "ゲーム"}, {Name: "茶道"}}},
	}
	for _, member := range members {
		db.Create(&member)
	}

レコード追加はdb.Create()で行う。あらかじめ Member 構造体にデータを代入しておき、引数でそのアドレスを指定する。
Hobbies の値も同時に指定すると、member, hobby の両テーブルにレコードが挿入される。

すべてのレコードを取得

	var allMembers []Member
	db.Find(&allMembers)
	fmt.Println(allMembers)

結果を受けるMember型の空のスライスを用意しておき、db.Find()の引数でそのアドレスを渡す。

条件を指定してレコードを取得

	var miku Member
	db.Where("name = ?", "ミク").First(&miku)
	db.Model(&miku).Related(&miku.Hobbies)
	fmt.Printf("%#v\n", miku)

db.Where()で検索条件を指定。結果を受けるMember型の変数を用意しておき、First()の引数でそのアドレスを渡して結果を受け取る。
ただしこれだけだと member テーブルの値のみが取得され、関係テーブル(hobby)への問い合わせは発生しない。
db.Model().Related()を実行した時点で、関連テーブル(hobby)の情報が 取得できる。


GORMのサイトのマニュアルがなかなかきちんと説明してあるので、詳しくはそちらを参照するといいと思う。