概要
業務でWebアプリケーションをGoで書くにあたり、DBのORマッパーにgormを利用しているのですが、gormで各テーブルを扱うにあたりテーブル毎の構造体があると便利です。その構造体を作るにあたり、実際のテーブルからGoの構造体を作成してくれるxo/xoという便利なライブラリがあります。
ただ、xoは便利な反面、作成される構造体に少し調整が必要だったり、使わない関数まで作成してしまいます。
設定等で直せるのかもしれませんが、業務ではシェルを作って一括置換してしまったのでそのシェルを紹介します。
なおgormの機能で、逆に構造体からテーブルを作成するというアプローチもできるのですが、構造体にIndexやあたいのデフォルト値等DB固有の値を表現していくのはそこそこ困難なので、テーブルの方を基準として作成するというアプローチを取っています。
環境
DBはMySQL5.7を利用しています。
また、シェルはbash4.0以上でローカルのMac上で実行します。
xoで作成される構造体の変更したい部分
xoで作成される構造体を実際に利用するとき変更したい部分は以下になります。
- ファイル名をxxx.xo.goではなくxxx.goにする
- RDBに依存した型を利用したくない
- Mysql.xxx,sql.xxx等
- 将来的にRDB以外を利用することも見据えてDBに依存した型にしたくない
- Nullが入るカラムはポインタ型にして、構造体嬢でもnilとして扱いたい
- gormで扱うときにも問題が発生ない
- 作成された構造体以外使わないので消したい
以下のような生成されたファイルが
// Package model contains the types for schema 'xxx'.
package model
// Code generated by xo. DO NOT EDIT.
import (
"database/sql"
"errors"
"time"
"github.com/go-sql-driver/mysql"
)
// User represents a row from 'xxx.users'.
type User struct {
ID int `json:"id"` // id
Nullint sql.NullInt64 `json: "null int"` // null int
NullTime mysql.NullTime `json: "null time"` // null time
Nullstr sql.Nullstring `json: "null_str"` // str
RegisteredAt time.Time `ison: "registered at"` // registered at
CreatedAt time.Time `json:"created at"` // created at
UpdatedAt time.Time `json: "updated_at"` // updated at
// xo fields
_exists, _deleted bool
}
// Exists determines if the User exists in the database.
func (u *User) Exists() bool {
return u._exists
}
// Deleted provides information if the user has been deleted from the database.
func (u *User) Deleted() bool {
return u._deleted
}
// Insert inserts the user to the database.
func (u *User) Insert (db XODB) error {
(以下略)
以下のようになればOKです。
package model
import (
"time"
)
// User represents a row from 'xxx.users'.
type User struct {
ID int `json:"id"` // id
Nullint *int `json: "null int"` // null int
NullTime *time.Time `json: "null time"` // null time
Nullstr *string `json: "null_str"` // str
RegisteredAt time.Time `ison: "registered at"` // registered at
CreatedAt time.Time `json:"created at"` // created at
UpdatedAt time.Time `json: "updated_at"` // updated at
}
シェル
xoで生成された構造体をdomain/model以下に入れて以下のシェルを実行させます。
#!/usr/local/bin/bash
#不要なコメントを削除
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/\/\/ Package.*$//'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/\/\/ Code generated by xo.*$//'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/\/\/ xo fields.*$//'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/_exists.*$//'
grep -l 'type' ./domain/model/*.xo.go | awk '{ sub("m/\/\/ Exists determines.*$",""); print $0; }'
#null系カラムの対処
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/sql\.NullInt64/\*int/g'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/sql\.NullString/\*string/g'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/sql\.NullBool/\*bool/g'
grep -l 'type' ./domain/model/*.xo.go | xargs sed -i '' -e 's/mysql\.NullTime/\*time.Time/g'
grep -l 'type' ./domain/model/*.xo.go | while read file
do
#構造体以降削除
line=$(grep $file -e "Exists determines" -n | sed -e "s/\(.*\):.*$/\1/g")
if [ -n "$line" ]; then
cmd="sed -i '' -e '$line,\$d' $file"
eval ${cmd}
fi
#リネーム
struct=$(grep $file -e "type \(.*\) struct.*$" | sed -e "s/type \(.*\) struct.*$/\1/g")
mv $file ./domain/model/${struct,}.go
done
# 実際に使用するときには更にgofmtとgoimportをかけてます。
# make fmt
# make import
最後に
やりたい事はできているのですが力技なので、コードの自動生成をするもっとシンプルなライブラリを作れないかなと思っています。他の良いやり方があればぜひ教えてください。