LoginSignup
0
3

More than 3 years have passed since last update.

GORM 公式ドキュメント メモ・その1「モデル、CRUDについて」

Last updated at Posted at 2020-08-14

はじめに

GORM公式ドキュメントを読んだ際、頭に残すために書き写したものです。
ほとんど公式通りですが、パッと見で理解しづらかった言い回しなどを書き直しています。

インストール

go get -u github.com/jinzhu/gorm

モデル

モデルの例

type User struct {
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // フィールドサイズを255にセットします
  MemberNumber *string `gorm:"unique;not null"` // MemberNumberをuniqueかつnot nullにセットします
  Num          int     `gorm:"AUTO_INCREMENT"` // Numを自動インクリメントにセットします
  Address      string  `gorm:"index:addr"` // `addr`という名前のインデックスを作ります
  IgnoreMe     int     `gorm:"-"` // このフィールドは無視します
}

サポートされている構造体(struct)のタグ

上記モデルの
gorm:"type:varchar(100);unique_index"
でにある
・type
・unique_index
などがタグ。

タグ 説明
Column カラム名を指定します
Type カラムのデータ型を指定します
Size カラムサイズのサイズを指定します。デフォルトは255です
PRIMARY_KEY カラムを主キーに指定します
UNIQUE カラムにユニーク制約を指定します
DEFAULT カラムのデフォルト値を指定します
PRECISION カラムの精度を指定します
NOT NULL カラムにNOT NULL制約を指定します
AUTO_INCREMENT カラムに自動インクリメントかそうでないかを指定します
INDEX 名前有りか名前無しでインデックスを作成します。同名のインデックスは複合インデックスになります。
UNIQUE_INDEX INDEXと同様にユニークインデックスを作成します
EMBEDDED 埋め込み構造体に設定します
EMBEDDED_PREFIX 埋め込み構造体のプレフィックス名を設定します
- このフィールドを無視します

モデルの作成

// gorm.Modelの定義
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

// `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`フィールドを`User`モデルに注入します
type User struct {
  gorm.Model
  Name string
}

// gorm.Model無しにモデルを宣言する場合
type User struct {
  ID   int // `ID`という名前のフィールドはデフォルトで主キーとして扱われます
  Name string
}

テーブル名

テーブル名は、デフォルトではStruct名の複数形が使われます。

type User struct {} // デフォルトのテーブル名は`users`です

// Userのテーブル名を`profiles`にする
func (User) TableName() string {
  return "profiles"
}

func (u User) TableName() string {
  if u.Role == "admin" {
    return "admin_users"
  } else {
    return "users"
  }
}

// テーブル名を複数形にしない。trueにすると、`User`のテーブル名は`user`になります
db.SingularTable(true)

カラム

カラム名はスネークケースになります。

type User struct {
  ID        uint      // カラム名は `id`
  Name      string    // カラム名は `name`
  Birthday  time.Time // カラム名は `birthday`
  CreatedAt time.Time // カラム名は `created_at`
}

カラム名をオーバーライドする事も可能。

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // カラム名を `beast_id` に設定する
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // カラム名を `day_of_the_beast` に設定する
  Age         int64     `gorm:"column:age_of_the_beast"` // カラム名を `age_of_the_beast` に設定する
}

タイムスタンプ

CreatedAt

CreatedAtフィールドの持つモデルでは、レコードの初回生成時に現在時刻が設定されます。

db.Create(&user) // `CreatedAt`には現在時刻が設定されます

UpdatedAt

UpdatedAtフィールドを持つモデルでは、レコード保存時に現在時刻が設定されます。

db.Save(&user) // `UpdatedAt`に現在時刻を設定します
db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`に現在時刻を設定します

DeletedAt

モデルにDeletedAtフィールドが存在する場合、Deleteが呼ばれても実際にはデータベースからデータは削除されません。
代わりにDeletedAtにDeleteが呼ばれた時の時刻がセットされます。

db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// ソフトデリートされたレコードはクエリ実行時に無視されます
db.Where("age = 20").Find(&user)
//// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

削除された(deleted_at IS NOT NULL)レコードも取得したい場合はUnscopedを使用します。

db.Unscoped().Where("age = 20").Find(&users)
//// SELECT * FROM users WHERE age = 20;

また、ソフトデリートではなく完全に削除したい場合もUnscopedを使用します。

db.Unscoped().Delete(&order)
//// DELETE FROM orders WHERE id=10;

データベース

公式にサポートされているのは以下4つです。
* MySQL
* PostgreSQL
* SQLite
* SQL Server

MySQL

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
  db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
  defer db.Close()
}

PostgreSQL

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/postgres"
)

func main() {
  db, err := gorm.Open("postgres", "host=myhost port=myport user=gorm dbname=gorm password=mypassword")
  defer db.Close()
}

SQLite3

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/sqlite"
)

func main() {
  db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
  defer db.Close()
}

SQL Server

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/mssql"
)

func main() {
  db, err := gorm.Open("mssql", "sqlserver://username:password@localhost:1433?database=dbname")
  defer db.Close()
}

CRUD

Create

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
db.NewRecord(user) // => 主キーが空の場合に `true` を返します。
db.Create(&user)
db.NewRecord(user) // => `user` が作られた後に `false` を返します。

デフォルト値を設定する事もできます。

type Animal struct {
  ID   int64
  Name string `gorm:"default:'galeone'"`
  Age  int64
}

また、値がない場合やゼロ値の場合、そのフィールドはinsert時に実行されるSQLには含まれません。
レコードの挿入後の戻り値には含まれます。
go
var animal = Animal{Age: 99, Name: ""}
db.Create(&animal)
//// INSERT INTO animals("age") values('99');
//// SELECT name from animals WHERE ID=111; // 返却された主キーは111です。
//// animal.Name => 'galeone'

デフォルト値ではなくゼロ値をそのまま保存したい場合は、ポインタかscanner/valuerを利用します。

// ポインタを利用する場合
type User struct {
  gorm.Model
  Name string
  Age  *int `gorm:"default:18"`
}

// scanner/valuerを利用する場合
type User struct {
  gorm.Model
  Name string
  Age  sql.NullInt64 `gorm:"default:18"`
}

フック(BeforeCreate)

BeforeCreate フックでフィールドの値を更新したい場合、scope.SetColumnが利用できます。

func (user *User) BeforeCreate(scope *gorm.Scope) error {
  scope.SetColumn("ID", uuid.New())
  return nil
}

重複時のオプションをSQLに設定する場合

// InsertのSQLに、オプションを設定できます。
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)
//// INSERT INTO products (name, code) VALUES ("name", "code") ON CONFLICT;

Update

Save

すべてのフィールドを更新します。

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)

//// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

Update

特定のフィールドだけ更新したい場合、 Update と Updates を使うことができます。

// ひとつのフィールドを更新します
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 条件付きでひとつのフィールドを更新します
db.Model(&user).Where("active = ?", true).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

// `map` で複数のフィールドを更新します(対象のフィールドのみ)
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// `struct` で複数のフィールドを更新します(空ではないフィールドのみ)
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

selectomitを使って下記のような事もできます。

db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

フック

BeforeUpdate, BeforeSaveを使ってフック時に値を更新したい場合にはscope.SetColumnが使えます。

func (user *User) BeforeSave(scope *gorm.Scope) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    scope.SetColumn("EncryptedPassword", pw)
  }
}

フックをスキップしたい場合

上記の更新処理は、BeforeUpdate, AfterUpdateメソッドを実行します。その結果更新時にUpdatedAtのタイムスタンプや 持っている Associations が更新されます。もしそれらのメソッドを呼びたくない場合はUpdateColumnとUpdateColumnsが使えます。

// Update single attribute, similar with `Update`
db.Model(&user).UpdateColumn("name", "hello")
//// UPDATE users SET name='hello' WHERE id = 111;

// Update multiple attributes, similar with `Updates`
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18 WHERE id = 111;

また、フックはバッチアップデート時は実行されません。

クエリをともなう更新

DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';

DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2';

DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2' AND quantity > 1;

Delete

db.Delete(&email)
//// DELETE from emails where id=10;

※注意
GORMはレコードを削除する際に主キーを使うので、主キーが空の場合、GORMはそのモデルの全レコードを削除してしまいます。

まとめて削除する

db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
//// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
//// DELETE from emails where email LIKE "%jinzhu%";¥

Soft Delete

モデルにDeletedAtフィールドがある場合、レコードはデータベースから物理削除されるのではなく、 DeletedAtに現在の時間がセットされます。

Query

GORM 公式ドキュメント メモ・その2「CRUDについて(Query)」
に記載。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3