【Go】ORMのxoでモデルを作成する

## はじめに
GoでORMのXOを使ってみます。TBLのSchemaからmodelを実装できます。

## 準備
TBLを準備する必要があるので、DockerでPostgresを起動します。

```dockerfile:docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: xodb
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:

```

```shell:実行
docker-compose up -d 
```

TBLを作成します。下記コマンドで作っていきます
```sql:
docker-compose exec db psql -U postgres -d xodb

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE
);
```

## ソースコード

```go:xoのpackageのinstall
~/go/src/go_xo$ go install github.com/xo/xo@latest  
```

```sql:modelを作成する
~/go/src/go_xo$ xo schema "pgsql://postgres:password@localhost:5432/xodb?sslmode=disable" -o ~/go/src/go_xo/models
```

:::note info
ここで初めて、modelが自動生成されます。ではみていきましょう！！
:::

```go:models/user.xo.go
package models

// Code generated by xo. DO NOT EDIT.

import (
	"context"
)

// User represents a row from 'public.users'.
type User struct {
	ID    int    `json:"id"`    // id
	Name  string `json:"name"`  // name
	Email string `json:"email"` // email
	// xo fields
	_exists, _deleted bool
}

// Exists returns true when the [User] exists in the database.
func (u *User) Exists() bool {
	return u._exists
}

// Deleted returns true when the [User] has been marked for deletion
// from the database.
func (u *User) Deleted() bool {
	return u._deleted
}

// Insert inserts the [User] to the database.
func (u *User) Insert(ctx context.Context, db DB) error {
	switch {
	case u._exists: // already exists
		return logerror(&ErrInsertFailed{ErrAlreadyExists})
	case u._deleted: // deleted
		return logerror(&ErrInsertFailed{ErrMarkedForDeletion})
	}
	// insert (primary key generated and returned by database)
	const sqlstr = `INSERT INTO public.users (` +
		`name, email` +
		`) VALUES (` +
		`$1, $2` +
		`) RETURNING id`
	// run
	logf(sqlstr, u.Name, u.Email)
	if err := db.QueryRowContext(ctx, sqlstr, u.Name, u.Email).Scan(&u.ID); err != nil {
		return logerror(err)
	}
	// set exists
	u._exists = true
	return nil
}

// Update updates a [User] in the database.
func (u *User) Update(ctx context.Context, db DB) error {
	switch {
	case !u._exists: // doesn't exist
		return logerror(&ErrUpdateFailed{ErrDoesNotExist})
	case u._deleted: // deleted
		return logerror(&ErrUpdateFailed{ErrMarkedForDeletion})
	}
	// update with composite primary key
	const sqlstr = `UPDATE public.users SET ` +
		`name = $1, email = $2 ` +
		`WHERE id = $3`
	// run
	logf(sqlstr, u.Name, u.Email, u.ID)
	if _, err := db.ExecContext(ctx, sqlstr, u.Name, u.Email, u.ID); err != nil {
		return logerror(err)
	}
	return nil
}

// Save saves the [User] to the database.
func (u *User) Save(ctx context.Context, db DB) error {
	if u.Exists() {
		return u.Update(ctx, db)
	}
	return u.Insert(ctx, db)
}

// Upsert performs an upsert for [User].
func (u *User) Upsert(ctx context.Context, db DB) error {
	switch {
	case u._deleted: // deleted
		return logerror(&ErrUpsertFailed{ErrMarkedForDeletion})
	}
	// upsert
	const sqlstr = `INSERT INTO public.users (` +
		`id, name, email` +
		`) VALUES (` +
		`$1, $2, $3` +
		`)` +
		` ON CONFLICT (id) DO ` +
		`UPDATE SET ` +
		`name = EXCLUDED.name, email = EXCLUDED.email `
	// run
	logf(sqlstr, u.ID, u.Name, u.Email)
	if _, err := db.ExecContext(ctx, sqlstr, u.ID, u.Name, u.Email); err != nil {
		return logerror(err)
	}
	// set exists
	u._exists = true
	return nil
}

// Delete deletes the [User] from the database.
func (u *User) Delete(ctx context.Context, db DB) error {
	switch {
	case !u._exists: // doesn't exist
		return nil
	case u._deleted: // deleted
		return nil
	}
	// delete with single primary key
	const sqlstr = `DELETE FROM public.users ` +
		`WHERE id = $1`
	// run
	logf(sqlstr, u.ID)
	if _, err := db.ExecContext(ctx, sqlstr, u.ID); err != nil {
		return logerror(err)
	}
	// set deleted
	u._deleted = true
	return nil
}

// UserByEmail retrieves a row from 'public.users' as a [User].
//
// Generated from index 'users_email_key'.
func UserByEmail(ctx context.Context, db DB, email string) (*User, error) {
	// query
	const sqlstr = `SELECT ` +
		`id, name, email ` +
		`FROM public.users ` +
		`WHERE email = $1`
	// run
	logf(sqlstr, email)
	u := User{
		_exists: true,
	}
	if err := db.QueryRowContext(ctx, sqlstr, email).Scan(&u.ID, &u.Name, &u.Email); err != nil {
		return nil, logerror(err)
	}
	return &u, nil
}

// UserByID retrieves a row from 'public.users' as a [User].
//
// Generated from index 'users_pkey'.
func UserByID(ctx context.Context, db DB, id int) (*User, error) {
	// query
	const sqlstr = `SELECT ` +
		`id, name, email ` +
		`FROM public.users ` +
		`WHERE id = $1`
	// run
	logf(sqlstr, id)
	u := User{
		_exists: true,
	}
	if err := db.QueryRowContext(ctx, sqlstr, id).Scan(&u.ID, &u.Name, &u.Email); err != nil {
		return nil, logerror(err)
	}
	return &u, nil
}
```

:::note info
結構自動生成で作ってくれますね。modelだけしか作られないかと思ったんですが、CRUD処理まで書いてくれるんですね。
:::

```go:main.go
package main

import (
	"context"
	"database/sql"
	"fmt"
	"go_xo/models"
	"log"

	_ "github.com/lib/pq"
)

// DBインターフェースを定義
type DB interface {
	Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
	Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
	QueryRow(ctx context.Context, query string, args ...interface{}) *sql.Row
}

func main() {
	// データベース接続
	db, err := sql.Open("postgres", "user=postgres password=password dbname=xodb sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// DBインターフェースを満たすようにラップ
	dbWrapper := &DBWrapper{db}

	// 新しいユーザーを作成
	user := models.User{
		Name:  "Alice",
		Email: "alice@example.com",
	}
	// contextを作成
	ctx := context.Background()

	// InsertメソッドにcontextとDBインターフェースを渡す
	err = user.Insert(ctx, dbWrapper)
	if err != nil {
		log.Fatal(err)
	}

	// ユーザーの取得
	fetchedUser, err := models.UserByID(ctx, dbWrapper, user.ID)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Fetched User: %+v\n", fetchedUser)
}

// DBWrapperはsql.DBをラップし、DBインターフェースを実装します
type DBWrapper struct {
	*sql.DB
}

func (db *DBWrapper) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	return db.DB.ExecContext(ctx, query, args...)
}

func (db *DBWrapper) Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	return db.DB.QueryContext(ctx, query, args...)
}

func (db *DBWrapper) QueryRow(ctx context.Context, query string, args ...interface{}) *sql.Row {
	return db.DB.QueryRowContext(ctx, query, args...)
}

```

```shell:実行結果
~/go/src/go_xo$ go run main.go                                                                                    
Fetched User: &{ID:1 Name:Alice Email:alice@example.com _exists:true _deleted:false}
```