gormで一時的にPreloadやsave_assosiationの関係性を構築する

この記事は[Wanoグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/wano-group) 20日目の記事になります。20ったら20です。


突然ですが、ここに、image_assetというモデルがあるとします。
集約です。



```json:image_asset
{
  "id": 13,
  "user_id": 1,
  "asset_id": 28,
  "asset": {
    "bucket": "988323dc-42e1-11eb-9c12-8c859050389c",
    "location": "852cb6b4-f0d0-4ace-a1c3-2966c7027985",
    "mime_type": "test/mimetype/image/repo",
    "upload_datetime": null,
    "filename": "988323e6-42e1-11eb-9c12-8c859050389c",
    "s3_storage_class_id": 1,
    "is_exist": false,
    "file_size_byte": null,
    "md5_hash": "8b1a5be6-5b44-45e0-9325-ec9428e378ff"
  },
  "image_asset_type_id": 1,
  "orig_image_asset_id": null,
  "width": 66780,
  "height": 12087,
  "image_asset_category_id": null,
  "from_tcj": false
}

```
image_assetはassetを持ちます。
image_assetとassetはmysqlのテーブル上も別ですが、集約なのでライフサイクル上も一緒とします。


```go

package mysqldto

import (
	"database/sql"
	"gopkg.in/guregu/null.v3"
	"time"
)

var (
	_ = time.Second
	_ = sql.LevelDefault
	_ = null.Bool{}
)



type ImageAsset struct {
	ID                   int        `gorm:"primary_key;AUTO_INCREMENT;column:id;type:uint;" json:"id"`
	CreateTime           *time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP;" json:"create_time"`
	UpdateTime           *time.Time `gorm:"column:update_time;type:timestamp;default:CURRENT_TIMESTAMP;" json:"update_time"`
	UserID               int        `gorm:"column:user_id;type:uint;" json:"user_id"`
	AssetID              int        `gorm:"column:asset_id;type:uint;" json:"asset_id"`
	ImageAssetTypeID     int        `gorm:"column:image_asset_type_id;type:utinyint;" json:"image_asset_type_id"`
	Width                int        `gorm:"column:width;type:uint;default:0;" json:"width"`
	Height               int        `gorm:"column:height;type:uint;default:0;" json:"height"`
	ImageAssetCategoryID null.Int   `gorm:"column:image_asset_category_id;type:utinyint;" json:"image_asset_category_id"`
	FromTcj              bool       `gorm:"column:from_tcj;type:utinyint;default:0;" json:"from_tcj"`
}


type Asset struct {
	ID               int         `gorm:"primary_key;AUTO_INCREMENT;column:id;type:uint;" json:"id"`
	CreateTime       *time.Time  `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP;" json:"create_time"`
	UpdateTime       *time.Time  `gorm:"column:update_time;type:timestamp;default:CURRENT_TIMESTAMP;" json:"update_time"`
	Bucket           string      `gorm:"column:bucket;type:varchar;size:100;" json:"bucket"`
	Location         string      `gorm:"column:location;type:varchar;size:1023;" json:"location"`
	MimeType         string      `gorm:"column:mime_type;type:varchar;size:50;" json:"mime_type"`
	UploadDatetime   null.Time   `gorm:"column:upload_datetime;type:datetime;" json:"upload_datetime"`
	Filename         null.String `gorm:"column:filename;type:varchar;size:255;" json:"filename"`
	IsExist          bool        `gorm:"column:is_exist;type:utinyint;default:1;" json:"is_exist"`
	S3StorageClassID int         `gorm:"column:s3_storage_class_id;type:utinyint;default:1;" json:"s_3_storage_class_id"`
	FileSizeByte     null.Int    `gorm:"column:file_size_byte;type:ubigint;" json:"file_size_byte"`
	Md5Hash          null.String `gorm:"column:md5_hash;type:varchar;size:512;" json:"md_5_hash"`
}

```
ただし、mysqlのdtoを表現する構造体は以上のように愚直に別テーブルでsave_associationとかのタグ魔法もないとします。
ImageAssetをAssetと共にSave,Getするにはどうするでしょうか?
まあ普通に別途クエリを書けばいいんですが...


## 上書き

```go
package mysql_repositoy


type imageAssetAggregate struct {
	*mysqldto.ImageAsset
	Asset mysqldto.Asset `gorm:"ForeignKey:AssetID;save_associations:true"`
}
...

	gormDB = gormDB.Table(`image_asset`).
		Preload(`Asset`).
		Joins(`INNER JOIN asset ON asset.id = image_asset.asset_id`)

```

gormのtipsでもなんでもないんですが、**単に一時的に構造体作って埋め込む**だけでも、gormの魔法タグを使った同時SaveやPreloadによる雑な子集約の収集は実現できます。
  

...なんでこういうことしてるかというと、modelとdtoの分離の文脈とか、dtoは純粋にmysqlテーブルの形にしておきたいとか、でもでもActiveRecordっぽいEager Loadingは欲しいときあるよね!とかいろいろ経緯や事情があるんですが、まあこういうこともできるよという話でした。