Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

xoでできたgoの構造体をxoやsqlに依存しないモデルに書き換えるシェル

More than 1 year has passed since last update.

概要

業務でWebアプリケーションをGoで書くにあたり、DBのORマッパーにgormを利用しているのですが、gormで各テーブルを扱うにあたりテーブル毎の構造体があると便利です。その構造体を作るにあたり、実際のテーブルからGoの構造体を作成してくれるxo/xoという便利なライブラリがあります。

ただ、xoは便利な反面、作成される構造体に少し調整が必要だったり、使わない関数まで作成してしまいます。
設定等で直せるのかもしれませんが、業務ではシェルを作って一括置換してしまったのでそのシェルを紹介します。

なおgormの機能で、逆に構造体からテーブルを作成するというアプローチもできるのですが、構造体にIndexやあたいのデフォルト値等DB固有の値を表現していくのはそこそこ困難なので、テーブルの方を基準として作成するというアプローチを取っています。

環境

DBはMySQL5.7を利用しています。
また、シェルはbash4.0以上でローカルのMac上で実行します。

(参考)Macのbashを4.x系に変更する

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

最後に

やりたい事はできているのですが力技なので、コードの自動生成をするもっとシンプルなライブラリを作れないかなと思っています。他の良いやり方があればぜひ教えてください。

andfactory
Smartphone Idea Companyとして、人々の生活に「&(アンド)」を届ける。
https://andfactory.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away