CodeSampleX

サンプル

gorm.io/gorm v1.31.2: Measure association loading, filtering, creation, and deletion behavior in GORM 1.31.2 with the pinned in-memory SQLite driver

検証済みサンプル — golang gorm.io/gorm v1.31.2: Measure association loading, filtering, creation, and deletion behavior in GORM 1.31.2 with the pinned in-memory…

sha256:c46b7eb8fabdc140ce3ea2b636aaf5d4c76312d0d78f9724cfaf8fcc350978a8

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。 MIT-0

実行証拠

宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。

証拠の基準
署名済みコントラクト合格
検証レシート
3
ビルドした署名鍵
2
宣言された環境 go linux x64 go go go

検証実行環境

環境 コントラクト ステージ 実行日
go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-17
go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-18
go 1.26 · linux alpine/x64 · docker ed25519:c1973797be207ac4 FAIL compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS
CONTAINER_RUN · golang@1golang:1.26-alpine@sha256:28d89ee9cc0f…
2026-09-07

ケース

HOW
ゴール
Measure association loading, filtering, creation, and deletion behavior in GORM 1.31.2 with the pinned in-memory SQLite driver
パッケージ
シンボル
  • gorm.Open
  • DB.Preload
  • DB.Joins
  • DB.Where
  • DB.Find
  • DB.Delete
  • DB.Create
  • DB.Select
  • DB.Omit
  • clause.Associations
環境
go
作成日
2026-08-17T03:54:31Z

コントラクト

  1. assert Preload with a condition returns both parent records and leaves the nonmatching parent's child slice empty
  2. assert Preload plus a root Where on the unjoined child table returns a no such column SQL error
  3. assert Joins on the tested HasMany slice relation panics during reflection instead of populating the slice
  4. assert Joins on the tested HasOne relation returns one matching parent and populates its nested struct
  5. assert an explicit SQL join with Distinct filters the parent records and conditional Preload attaches the matching child
  6. assert plain Delete leaves the seeded child rows present in the test database
  7. assert Select with clause.Associations on Delete removes the seeded child rows
  8. assert Omit with clause.Associations on Create inserts only the parent row

ファイル

  • NOTES.md
  • csx.json
  • go.mod
  • go.sum
  • relations.go
  • relations_test.go

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
# GORM 1.31.2 association contract

The contract uses GORM 1.31.2 and the pinned pure-Go in-memory SQLite stack.
With the seeded `User`, `Orders`, and `Profile` models it measures these exact
paths: conditional `Preload` keeps both parents; a root `Where` on the unjoined
orders table errors; `Joins("Orders")` panics for the tested slice relation;
`Joins("Profile")` populates the tested single relation; an explicit SQL join
filters the parent; plain `Delete` leaves the seeded child rows; association-
selected delete removes them; and association-omitted create inserts no child
rows.

Those observations are scoped to the pinned versions, schema, and test setup.
csx.json
{"case":{"believed":"A conditional Preload filters root parent records and Joins can load a HasMany slice association in GORM 1.31.2.","caseId":"case:sha256:a1e979780cbcceb5eed9dc6deb04e27255b6a6bb675d423ba54376a0d6288cd5","contract":["assert Preload with a condition returns both parent records and leaves the nonmatching parent's child slice empty","assert Preload plus a root Where on the unjoined child table returns a no such column SQL error","assert Joins on the tested HasMany slice relation panics during reflection instead of populating the slice","assert Joins on the tested HasOne relation returns one matching parent and populates its nested struct","assert an explicit SQL join with Distinct filters the parent records and conditional Preload attaches the matching child","assert plain Delete leaves the seeded child rows present in the test database","assert Select with clause.Associations on Delete removes the seeded child rows","assert Omit with clause.Associations on Create inserts only the parent row"],"goal":"Measure association loading, filtering, creation, and deletion behavior in GORM 1.31.2 with the pinned in-memory SQLite driver","kind":"HOW","packages":["pkg:golang/gorm.io/gorm@v1.31.2","pkg:golang/github.com/glebarez/sqlite@v1.11.0","pkg:golang/modernc.org/sqlite@v1.56.0"],"schemaVersion":1,"symbols":["gorm.Open","DB.Preload","DB.Joins","DB.Where","DB.Find","DB.Delete","DB.Create","DB.Select","DB.Omit","clause.Associations"]},"contractCommand":["go","test","-p=1","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"go","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/gorm.io/gorm@v1.31.2","pkg:golang/github.com/glebarez/sqlite@v1.11.0","pkg:golang/modernc.org/sqlite@v1.56.0"],"schemaVersion":1,"symbols":["gorm.Open","DB.Preload","DB.Joins","DB.Where","DB.Find","DB.Delete","DB.Create","DB.Select","DB.Omit","clause.Associations"],"verifierAdapter":"golang@1"}
go.mod
module example.com/gormapi

go 1.25.0

require (
	github.com/glebarez/sqlite v1.11.0
	gorm.io/gorm v1.31.2
)

require (
	github.com/dustin/go-humanize v1.0.1 // indirect
	github.com/glebarez/go-sqlite v1.21.2 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/jinzhu/now v1.1.5 // indirect
	github.com/mattn/go-isatty v0.0.24 // indirect
	github.com/ncruces/go-strftime v1.0.0 // indirect
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
	golang.org/x/sys v0.47.0 // indirect
	golang.org/x/text v0.20.0 // indirect
	modernc.org/libc v1.74.4 // indirect
	modernc.org/mathutil v1.7.1 // indirect
	modernc.org/memory v1.11.0 // indirect
	modernc.org/sqlite v1.56.0 // indirect
)
go.sum
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxY4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
relations.go
package gormapi

import (
	"time"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// User is the root entity in 1:1 and 1:N relations.
type User struct {
	ID        uint      `gorm:"primaryKey"`
	Name      string    `gorm:"not null"`
	Profile   Profile   // HasOne (1:1)
	Orders    []Order   // HasMany (1:N)
	CreatedAt time.Time
}

// Profile represents a 1:1 HasOne relation belonging to a User.
type Profile struct {
	ID     uint   `gorm:"primaryKey"`
	UserID uint   `gorm:"not null;index"`
	Bio    string `gorm:"not null"`
}

// Order represents a 1:N HasMany relation belonging to a User.
type Order struct {
	ID        uint      `gorm:"primaryKey"`
	UserID    uint      `gorm:"not null;index"`
	Amount    int       `gorm:"not null"`
	Status    string    `gorm:"not null"`
	CreatedAt time.Time
}

// OpenInMemoryDB initializes an isolated in-memory SQLite database pinned to a single connection.
func OpenInMemoryDB() (*gorm.DB, error) {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		return nil, err
	}

	sqlDB, err := db.DB()
	if err != nil {
		return nil, err
	}
	sqlDB.SetMaxOpenConns(1)

	if err := db.AutoMigrate(&User{}, &Profile{}, &Order{}); err != nil {
		return nil, err
	}

	return db, nil
}
relations_test.go
package gormapi

import (
	"strings"
	"testing"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func seedDatabase(t *testing.T, db *gorm.DB) (User, User) {
	t.Helper()

	u1 := User{
		Name: "Alice",
		Profile: Profile{Bio: "Gopher"},
		Orders: []Order{
			{Amount: 100, Status: "completed"},
			{Amount: 50, Status: "pending"},
		},
	}
	u2 := User{
		Name: "Bob",
		Profile: Profile{Bio: "Rustacean"},
		Orders: []Order{
			{Amount: 20, Status: "pending"},
		},
	}

	if err := db.Create(&u1).Error; err != nil {
		t.Fatalf("seed u1 failed: %v", err)
	}
	if err := db.Create(&u2).Error; err != nil {
		t.Fatalf("seed u2 failed: %v", err)
	}

	return u1, u2
}

func TestPreloadConditionDoesNotFilterParent(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, _ = seedDatabase(t, db)

	// Contract 1: Preload with a condition does NOT filter the parent rows.
	// Naive expectation: Preload("Orders", "status = ?", "completed") returns only users who have completed orders (1 user: Alice).
	// Reality: It returns all 2 users. Bob is included with an empty slice Orders: [].
	var preloadedUsers []User
	if err := db.Preload("Orders", "status = ?", "completed").Find(&preloadedUsers).Error; err != nil {
		t.Fatalf("preload query failed: %v", err)
	}

	if len(preloadedUsers) != 2 {
		t.Fatalf("expected 2 users returned because Preload does not filter root records, got %d", len(preloadedUsers))
	}

	for _, u := range preloadedUsers {
		if u.Name == "Alice" && len(u.Orders) != 1 {
			t.Fatalf("Alice should have 1 completed order, got %d", len(u.Orders))
		}
		if u.Name == "Bob" && len(u.Orders) != 0 {
			t.Fatalf("Bob should have 0 completed orders in preloaded slice, got %d", len(u.Orders))
		}
	}
}

func TestPreloadWhereOnChildFailsSQL(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, _ = seedDatabase(t, db)

	// Contract 2: Where clause on child column with Preload fails at SQL execution because Preload does not JOIN.
	// Naive expectation: db.Preload("Orders").Where("orders.status = ?", "completed") filters users by order status.
	// Reality: SQL error: "no such column: orders.status" because the parent query is a bare "SELECT * FROM users WHERE orders.status = ...".
	var failedWhereUsers []User
	errWhere := db.Preload("Orders").Where("orders.status = ?", "completed").Find(&failedWhereUsers).Error
	if errWhere == nil {
		t.Fatalf("expected error querying unjoined child column in root Where, got nil")
	}
	if !strings.Contains(errWhere.Error(), "no such column: orders.status") {
		t.Fatalf("expected 'no such column: orders.status' error, got %v", errWhere)
	}
}

func TestJoinsOnHasManyPanicsInReflection(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, _ = seedDatabase(t, db)

	// Contract 3: Joins on HasMany relation panics at runtime during struct reflection scanning.
	// Naive expectation: db.Joins("Orders") performs a join and scans child orders into slice.
	// Reality: Joins on HasMany (slice field) triggers reflect panic because Joins only supports HasOne/BelongsTo.
	defer func() {
		r := recover()
		if r == nil {
			t.Fatalf("expected panic when calling Joins on HasMany relation")
		}
		msg := ""
		if str, ok := r.(string); ok {
			msg = str
		} else if e, ok := r.(error); ok {
			msg = e.Error()
		}
		if !strings.Contains(msg, "reflect.Value.Field on slice Value") && !strings.Contains(msg, "slice") {
			t.Fatalf("expected slice reflection panic, got %v", r)
		}
	}()

	var joinsHasMany []User
	_ = db.Joins("Orders").Find(&joinsHasMany).Error
}

func TestJoinsOnHasOnePopulatesStruct(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, _ = seedDatabase(t, db)

	// Contract 4: Joins works for HasOne / BelongsTo (single struct) and populates the nested struct in a single query.
	var joinsHasOne []User
	errJoins := db.Joins("Profile").Where("Profile.bio = ?", "Gopher").Find(&joinsHasOne).Error
	if errJoins != nil {
		t.Fatalf("Joins HasOne error: %v", errJoins)
	}
	if len(joinsHasOne) != 1 {
		t.Fatalf("expected 1 user (Alice), got %d", len(joinsHasOne))
	}
	if joinsHasOne[0].Name != "Alice" || joinsHasOne[0].Profile.Bio != "Gopher" {
		t.Fatalf("expected Alice with Profile.Bio='Gopher', got %+v", joinsHasOne[0])
	}
}

func TestExplicitJoinFiltersParentRecords(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, _ = seedDatabase(t, db)

	// Contract 5: Filtering parent records by HasMany attributes requires explicit SQL JOIN or subquery.
	var filteredUsers []User
	errExplicitJoin := db.Joins("INNER JOIN orders ON orders.user_id = users.id AND orders.status = ?", "completed").
		Distinct().
		Preload("Orders", "status = ?", "completed").
		Find(&filteredUsers).Error
	if errExplicitJoin != nil {
		t.Fatalf("explicit join error: %v", errExplicitJoin)
	}
	if len(filteredUsers) != 1 || filteredUsers[0].Name != "Alice" {
		t.Fatalf("expected exactly 1 user (Alice) with explicit join, got %d (%+v)", len(filteredUsers), filteredUsers)
	}
	if len(filteredUsers[0].Orders) != 1 || filteredUsers[0].Orders[0].Status != "completed" {
		t.Fatalf("expected 1 completed order attached to Alice, got %+v", filteredUsers[0].Orders)
	}
}

func TestPlainDeleteDoesNotCascadeAssociations(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	u1, _ := seedDatabase(t, db)

	// Contract 6: Plain db.Delete does NOT cascade delete child records in HasMany or HasOne relations.
	// Naive expectation: db.Delete(&u1) deletes Alice and her orders and profile.
	// Reality: Only row in 'users' is deleted. The 2 orders and 1 profile remain orphaned.
	var orderCountBefore, profileCountBefore int64
	db.Model(&Order{}).Where("user_id = ?", u1.ID).Count(&orderCountBefore)
	db.Model(&Profile{}).Where("user_id = ?", u1.ID).Count(&profileCountBefore)
	if orderCountBefore != 2 || profileCountBefore != 1 {
		t.Fatalf("unexpected child counts before delete: orders=%d, profile=%d", orderCountBefore, profileCountBefore)
	}

	if err := db.Delete(&u1).Error; err != nil {
		t.Fatalf("delete u1: %v", err)
	}

	var orderCountAfter, profileCountAfter int64
	db.Model(&Order{}).Where("user_id = ?", u1.ID).Count(&orderCountAfter)
	db.Model(&Profile{}).Where("user_id = ?", u1.ID).Count(&profileCountAfter)
	if orderCountAfter != 2 || profileCountAfter != 1 {
		t.Fatalf("plain Delete must not remove child records; expected orders=2, profile=1, got orders=%d, profile=%d", orderCountAfter, profileCountAfter)
	}
}

func TestSelectClauseAssociationsCascadesDelete(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}
	_, u2 := seedDatabase(t, db)

	// Contract 7: Select(clause.Associations).Delete cascades association deletion through GORM.
	var bobOrdersBefore, bobProfileBefore int64
	db.Model(&Order{}).Where("user_id = ?", u2.ID).Count(&bobOrdersBefore)
	db.Model(&Profile{}).Where("user_id = ?", u2.ID).Count(&bobProfileBefore)
	if bobOrdersBefore != 1 || bobProfileBefore != 1 {
		t.Fatalf("unexpected Bob child counts: orders=%d, profile=%d", bobOrdersBefore, bobProfileBefore)
	}

	if err := db.Select(clause.Associations).Delete(&u2).Error; err != nil {
		t.Fatalf("cascade delete u2: %v", err)
	}

	var bobOrdersAfter, bobProfileAfter int64
	db.Model(&Order{}).Where("user_id = ?", u2.ID).Count(&bobOrdersAfter)
	db.Model(&Profile{}).Where("user_id = ?", u2.ID).Count(&bobProfileAfter)
	if bobOrdersAfter != 0 || bobProfileAfter != 0 {
		t.Fatalf("Select(clause.Associations).Delete should remove child records; expected orders=0, profile=0, got orders=%d, profile=%d", bobOrdersAfter, bobProfileAfter)
	}
}

func TestOmitClauseAssociationsLeavesChildEmpty(t *testing.T) {
	db, err := OpenInMemoryDB()
	if err != nil {
		t.Fatalf("setup db: %v", err)
	}

	// Contract 8: Omit(clause.Associations).Create creates only the parent and leaves associations unpersisted.
	u3 := User{
		Name: "Charlie",
		Profile: Profile{Bio: "Unsaved Profile"},
		Orders: []Order{
			{Amount: 99, Status: "pending"},
		},
	}
	if err := db.Omit(clause.Associations).Create(&u3).Error; err != nil {
		t.Fatalf("omit create: %v", err)
	}
	if u3.ID == 0 {
		t.Fatalf("parent ID should be set after Create")
	}

	var charlieOrders int64
	db.Model(&Order{}).Where("user_id = ?", u3.ID).Count(&charlieOrders)
	if charlieOrders != 0 {
		t.Fatalf("Omit(clause.Associations) must not insert child orders, got count %d", charlieOrders)
	}

	var charlieProfiles int64
	db.Model(&Profile{}).Where("user_id = ?", u3.ID).Count(&charlieProfiles)
	if charlieProfiles != 0 {
		t.Fatalf("Omit(clause.Associations) must not insert child profile, got count %d", charlieProfiles)
	}
}

オリジンシーダー

csx-seed