From 6ad467c259ba32247a3aae03aaf1fd21163742e5 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Thu, 25 Aug 2016 23:50:45 -0700 Subject: [PATCH 01/25] Started relationship set operations --- templates/relationship_to_many_setops.tpl | 36 +++++++++++++++++++ templates/relationship_to_one_setops.tpl | 25 +++++++++++++ .../relationship_to_many_setops.tpl | 0 templates_test/relationship_to_one_setops.tpl | 0 4 files changed, 61 insertions(+) create mode 100644 templates/relationship_to_many_setops.tpl create mode 100644 templates/relationship_to_one_setops.tpl create mode 100644 templates_test/relationship_to_many_setops.tpl create mode 100644 templates_test/relationship_to_one_setops.tpl diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl new file mode 100644 index 0000000..cdfb177 --- /dev/null +++ b/templates/relationship_to_many_setops.tpl @@ -0,0 +1,36 @@ +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- $table := .Table -}} + {{- range .Table.ToManyRelationships -}} + {{- $varNameSingular := .ForeignTable | singular | camelCase -}} + {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} +{{- template "relationship_to_one_setops_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}} + {{- else -}} + {{- $rel := textsFromRelationship $dot.Tables $table . -}} +// Add{{$rel.Function.Name}} adds the given related objects to the existing relationships +// of the {{$table.Name | singular}}, optionally inserting them as new records. +// Appends related to R.{{$rel.Function.Name}}. +func (r *{{$rel.LocalTable.NameGo}}Loaded) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { + return nil +} + +{{if .ForeignColumnNullable -}} +// Set{{$rel.Function.Name}} removes all previously related items of the +// {{$table.Name | singular}} replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Replaces R.{{$rel.Function.Name}} with related. +func (r *{{$rel.LocalTable.NameGo}}Loaded) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { + return nil +} + +// Remove{{$rel.Function.Name}} relationships from objects passed in. +// Removes related items from R.{{$rel.Function.Name}}. +func (r *{{$rel.LocalTable.NameGo}}Loaded) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error { + return nil +} +{{end -}} + +{{end -}}{{- /* if unique foreign key */ -}} +{{- end -}}{{- /* range relationships */ -}} +{{- end -}}{{- /* outer if join table */ -}} diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl new file mode 100644 index 0000000..e0da4ae --- /dev/null +++ b/templates/relationship_to_one_setops.tpl @@ -0,0 +1,25 @@ +{{- define "relationship_to_one_setops_helper" -}} +{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} +// Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item. +// Sets R.{{.Function.Name}} to related. +func (r *{{.LocalTable.NameGo}}Loaded) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { + return nil +} + +{{if .ForeignKey.Nullable -}} +// Remove{{.Function.Name}} relationship. +// Sets R.{{.Function.Name}} to nil. +func (r *{{.LocalTable.NameGo}}Loaded) Remove{{.Function.Name}}(exec boil.Executor) error { + return nil +} +{{end -}} + +{{end -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} +{{- template "relationship_to_one_setops_helper" $rel -}} +{{- end -}} +{{- end -}} diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl new file mode 100644 index 0000000..e69de29 diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl new file mode 100644 index 0000000..e69de29 From dc49fa4d59477d78d85d4c7669690dd9c2a985d7 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 07:59:28 -0700 Subject: [PATCH 02/25] Align some stuff properly - Begin tests --- templates/relationship_to_many_setops.tpl | 8 +++---- templates/relationship_to_one_setops.tpl | 18 +++++++++----- .../relationship_to_many_setops.tpl | 24 +++++++++++++++++++ templates_test/relationship_to_one_setops.tpl | 19 +++++++++++++++ 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index cdfb177..8a127a7 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -7,15 +7,16 @@ {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} {{- template "relationship_to_one_setops_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}} {{- else -}} - {{- $rel := textsFromRelationship $dot.Tables $table . -}} + {{- $rel := textsFromRelationship $dot.Tables $table .}} + // Add{{$rel.Function.Name}} adds the given related objects to the existing relationships // of the {{$table.Name | singular}}, optionally inserting them as new records. // Appends related to R.{{$rel.Function.Name}}. func (r *{{$rel.LocalTable.NameGo}}Loaded) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } +{{- if .ForeignColumnNullable}} -{{if .ForeignColumnNullable -}} // Set{{$rel.Function.Name}} removes all previously related items of the // {{$table.Name | singular}} replacing them completely with the passed // in related items, optionally inserting them as new records. @@ -30,7 +31,6 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Remove{{$rel.Function.Name}}(exec boi return nil } {{end -}} - -{{end -}}{{- /* if unique foreign key */ -}} +{{- end -}}{{- /* if unique foreign key */ -}} {{- end -}}{{- /* range relationships */ -}} {{- end -}}{{- /* outer if join table */ -}} diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index e0da4ae..21b54b1 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -1,20 +1,26 @@ {{- define "relationship_to_one_setops_helper" -}} -{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} +{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase}} + // Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item. // Sets R.{{.Function.Name}} to related. func (r *{{.LocalTable.NameGo}}Loaded) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { - return nil -} + r.{{.Function.Name}} = related + r.{{.Function.Name}}.{{.Function.ForeignAssignment}} = 5 + if insert { + return related.Insert() + } + + return exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, 5) +} +{{- if .ForeignKey.Nullable}} -{{if .ForeignKey.Nullable -}} // Remove{{.Function.Name}} relationship. // Sets R.{{.Function.Name}} to nil. func (r *{{.LocalTable.NameGo}}Loaded) Remove{{.Function.Name}}(exec boil.Executor) error { return nil } {{end -}} - -{{end -}} +{{- end -}} {{- if .Table.IsJoinTable -}} {{- else -}} {{- $dot := . -}} diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index e69de29..05afbef 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -0,0 +1,24 @@ +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- $table := .Table -}} + {{- range .Table.ToManyRelationships -}} + {{- $varNameSingular := .ForeignTable | singular | camelCase -}} + {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} +{{- template "relationship_to_one_setops_test_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}} + {{- else -}} + {{- $rel := textsFromRelationship $dot.Tables $table .}} + +func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing.T) { +} +{{if .ForeignColumnNullable}} + +func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing.T) { +} + +func test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}}(t *testing.T) { +} +{{end -}} +{{- end -}}{{- /* if unique foreign key */ -}} +{{- end -}}{{- /* range relationships */ -}} +{{- end -}}{{- /* outer if join table */ -}} diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index e69de29..aff1d1c 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -0,0 +1,19 @@ +{{- define "relationship_to_one_setops_test_helper" -}} +{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} +func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { +} +{{- if .ForeignKey.Nullable}} + +func test{{.LocalTable.NameGo}}ToOneRemoveOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { +} +{{end -}} +{{- end -}} +{{- if .Table.IsJoinTable -}} +{{- else -}} + {{- $dot := . -}} + {{- range .Table.FKeys -}} + {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table .}} + +{{template "relationship_to_one_setops_test_helper" $rel -}} +{{- end -}} +{{- end -}} From f489e22ab470eff93fb86e384156c64e69cff588 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 21:40:11 -0700 Subject: [PATCH 03/25] Rename Loaded -> R --- README.md | 2 +- boil/reflect.go | 6 +++--- boil/reflect_test.go | 8 ++++---- templates/00_struct.tpl | 6 +++--- templates/06_relationship_to_one_eager.tpl | 14 +++++++------- templates/07_relationship_to_many_eager.tpl | 20 ++++++++++---------- templates_test/relationship_to_many.tpl | 10 +++++----- templates_test/relationship_to_one.tpl | 10 +++++----- 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 90261e3..be4eb68 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ users, err := models.Users(db, Load("FavoriteMovies")) if err != nil { return err } -fmt.Println(len(users.Loaded.FavoriteMovies)) +fmt.Println(len(users.R.FavoriteMovies)) ``` ## How to boil your database diff --git a/boil/reflect.go b/boil/reflect.go index dbfb970..bfa5454 100644 --- a/boil/reflect.go +++ b/boil/reflect.go @@ -110,7 +110,7 @@ func (q *Query) BindFast(obj interface{}, titleCases map[string]string) error { // loadRelationships dynamically calls the template generated eager load // functions of the form: // -// func (t *TableLoaded) LoadRelationshipName(exec Executor, singular bool, obj interface{}) +// func (t *TableR) LoadRelationshipName(exec Executor, singular bool, obj interface{}) // // The arguments to this function are: // - t is not considered here, and is always passed nil. The function exists on a loaded @@ -125,11 +125,11 @@ func (q *Query) loadRelationships(obj interface{}, singular bool) error { typ = typ.Elem().Elem() } - rel, found := typ.FieldByName("Loaded") + rel, found := typ.FieldByName("R") // If the users object has no loaded struct, it must be // a custom object and we should not attempt to load any relationships. if !found { - return errors.New("load query mod was used but bound struct contained no Loaded field") + return errors.New("load query mod was used but bound struct contained no R field") } for _, relationship := range q.load { diff --git a/boil/reflect_test.go b/boil/reflect_test.go index 57e2de2..66495b8 100644 --- a/boil/reflect_test.go +++ b/boil/reflect_test.go @@ -166,9 +166,9 @@ func TestBindSingular(t *testing.T) { var loadFunctionCalled bool -type testLoadedStruct struct{} +type testRStruct struct{} -func (r *testLoadedStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error { +func (r *testRStruct) LoadTestOne(exec Executor, singular bool, obj interface{}) error { loadFunctionCalled = true return nil } @@ -179,7 +179,7 @@ func TestLoadRelationshipsSlice(t *testing.T) { testSlice := []*struct { ID int - Loaded *testLoadedStruct + R *testRStruct }{} q := Query{load: []string{"TestOne"}, executor: nil} @@ -198,7 +198,7 @@ func TestLoadRelationshipsSingular(t *testing.T) { testSingular := struct { ID int - Loaded *testLoadedStruct + R *testRStruct }{} q := Query{load: []string{"TestOne"}, executor: nil} diff --git a/templates/00_struct.tpl b/templates/00_struct.tpl index 50eabbf..b271e0e 100644 --- a/templates/00_struct.tpl +++ b/templates/00_struct.tpl @@ -11,15 +11,15 @@ type {{$modelName}} struct { {{end -}} {{- if .Table.IsJoinTable -}} {{- else}} - Loaded *{{$modelName}}Loaded `boil:"-" json:"-" toml:"-" yaml:"-"` + R *{{$modelName}}R `boil:"-" json:"-" toml:"-" yaml:"-"` {{end -}} } {{- $dot := . -}} {{- if .Table.IsJoinTable -}} {{- else}} -// {{$modelName}}Loaded are where relationships are eagerly loaded. -type {{$modelName}}Loaded struct { +// {{$modelName}}R is where relationships are stored. +type {{$modelName}}R struct { {{range .Table.FKeys -}} {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $dot.Table . -}} {{- template "relationship_to_one_struct_helper" $rel}} diff --git a/templates/06_relationship_to_one_eager.tpl b/templates/06_relationship_to_one_eager.tpl index baeecbc..7083366 100644 --- a/templates/06_relationship_to_one_eager.tpl +++ b/templates/06_relationship_to_one_eager.tpl @@ -3,7 +3,7 @@ {{- $slice := printf "%sSlice" .LocalTable.NameGo -}} // Load{{.Function.Name}} allows an eager lookup of values, cached into the // loaded structs of the objects. -func (r *{{.LocalTable.NameGo}}Loaded) Load{{.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error { +func (r *{{.LocalTable.NameGo}}R) Load{{.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error { var slice []*{{.LocalTable.NameGo}} var object *{{.LocalTable.NameGo}} @@ -45,20 +45,20 @@ func (r *{{.LocalTable.NameGo}}Loaded) Load{{.Function.Name}}(e boil.Executor, s } if singular && len(resultSlice) != 0 { - if object.Loaded == nil { - object.Loaded = &{{.LocalTable.NameGo}}Loaded{} + if object.R == nil { + object.R = &{{.LocalTable.NameGo}}R{} } - object.Loaded.{{.Function.Name}} = resultSlice[0] + object.R.{{.Function.Name}} = resultSlice[0] return nil } for _, foreign := range resultSlice { for _, local := range slice { if local.{{.Function.LocalAssignment}} == foreign.{{.Function.ForeignAssignment}} { - if local.Loaded == nil { - local.Loaded = &{{.LocalTable.NameGo}}Loaded{} + if local.R == nil { + local.R = &{{.LocalTable.NameGo}}R{} } - local.Loaded.{{.Function.Name}} = foreign + local.R.{{.Function.Name}} = foreign break } } diff --git a/templates/07_relationship_to_many_eager.tpl b/templates/07_relationship_to_many_eager.tpl index 986d995..0350990 100644 --- a/templates/07_relationship_to_many_eager.tpl +++ b/templates/07_relationship_to_many_eager.tpl @@ -10,7 +10,7 @@ {{- $slice := printf "%sSlice" $rel.LocalTable.NameGo -}} // Load{{$rel.Function.Name}} allows an eager lookup of values, cached into the // loaded structs of the objects. -func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error { +func (r *{{$rel.LocalTable.NameGo}}R) Load{{$rel.Function.Name}}(e boil.Executor, singular bool, {{$arg}} interface{}) error { var slice []*{{$rel.LocalTable.NameGo}} var object *{{$rel.LocalTable.NameGo}} @@ -82,10 +82,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe {{end}} if singular { - if object.Loaded == nil { - object.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{} + if object.R == nil { + object.R = &{{$rel.LocalTable.NameGo}}R{} } - object.Loaded.{{$rel.Function.Name}} = resultSlice + object.R.{{$rel.Function.Name}} = resultSlice return nil } @@ -94,10 +94,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe localJoinCol := localJoinCols[i] for _, local := range slice { if local.{{$rel.Function.LocalAssignment}} == localJoinCol { - if local.Loaded == nil { - local.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{} + if local.R == nil { + local.R = &{{$rel.LocalTable.NameGo}}R{} } - local.Loaded.{{$rel.Function.Name}} = append(local.Loaded.{{$rel.Function.Name}}, foreign) + local.R.{{$rel.Function.Name}} = append(local.R.{{$rel.Function.Name}}, foreign) break } } @@ -106,10 +106,10 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Load{{$rel.Function.Name}}(e boil.Exe for _, foreign := range resultSlice { for _, local := range slice { if local.{{$rel.Function.LocalAssignment}} == foreign.{{$rel.Function.ForeignAssignment}} { - if local.Loaded == nil { - local.Loaded = &{{$rel.LocalTable.NameGo}}Loaded{} + if local.R == nil { + local.R = &{{$rel.LocalTable.NameGo}}R{} } - local.Loaded.{{$rel.Function.Name}} = append(local.Loaded.{{$rel.Function.Name}}, foreign) + local.R.{{$rel.Function.Name}} = append(local.R.{{$rel.Function.Name}}, foreign) break } } diff --git a/templates_test/relationship_to_many.tpl b/templates_test/relationship_to_many.tpl index 8466fc7..be1148c 100644 --- a/templates_test/relationship_to_many.tpl +++ b/templates_test/relationship_to_many.tpl @@ -75,18 +75,18 @@ func test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}(t *testing.T) { } slice := {{$rel.LocalTable.NameGo}}Slice{&a} - if err = a.Loaded.Load{{$rel.Function.Name}}(tx, false, &slice); err != nil { + if err = a.R.Load{{$rel.Function.Name}}(tx, false, &slice); err != nil { t.Fatal(err) } - if got := len(a.Loaded.{{$rel.Function.Name}}); got != 2 { + if got := len(a.R.{{$rel.Function.Name}}); got != 2 { t.Error("number of eager loaded records wrong, got:", got) } - a.Loaded.{{$rel.Function.Name}} = nil - if err = a.Loaded.Load{{$rel.Function.Name}}(tx, true, &a); err != nil { + a.R.{{$rel.Function.Name}} = nil + if err = a.R.Load{{$rel.Function.Name}}(tx, true, &a); err != nil { t.Fatal(err) } - if got := len(a.Loaded.{{$rel.Function.Name}}); got != 2 { + if got := len(a.R.{{$rel.Function.Name}}); got != 2 { t.Error("number of eager loaded records wrong, got:", got) } diff --git a/templates_test/relationship_to_one.tpl b/templates_test/relationship_to_one.tpl index b33890d..8eb73dd 100644 --- a/templates_test/relationship_to_one.tpl +++ b/templates_test/relationship_to_one.tpl @@ -42,18 +42,18 @@ func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}( } slice := {{.LocalTable.NameGo}}Slice{&local} - if err = local.Loaded.Load{{.Function.Name}}(tx, false, &slice); err != nil { + if err = local.R.Load{{.Function.Name}}(tx, false, &slice); err != nil { t.Fatal(err) } - if local.Loaded.{{.Function.Name}} == nil { + if local.R.{{.Function.Name}} == nil { t.Error("struct should have been eager loaded") } - local.Loaded.{{.Function.Name}} = nil - if err = local.Loaded.Load{{.Function.Name}}(tx, true, &local); err != nil { + local.R.{{.Function.Name}} = nil + if err = local.R.Load{{.Function.Name}}(tx, true, &local); err != nil { t.Fatal(err) } - if local.Loaded.{{.Function.Name}} == nil { + if local.R.{{.Function.Name}} == nil { t.Error("struct should have been eager loaded") } } From c1962eb7ac9962de8f8d9a74aab28f2def062782 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 22:04:49 -0700 Subject: [PATCH 04/25] Get all the tests running properly --- templates/relationship_to_many_setops.tpl | 6 +- templates/relationship_to_one_setops.tpl | 17 +-- templates_test/relationship_to_one_setops.tpl | 49 ++++++++ templates_test/singleton/boil_suites_test.tpl | 109 +++++++++++++++++- 4 files changed, 165 insertions(+), 16 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index 8a127a7..3fe2701 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -12,7 +12,7 @@ // Add{{$rel.Function.Name}} adds the given related objects to the existing relationships // of the {{$table.Name | singular}}, optionally inserting them as new records. // Appends related to R.{{$rel.Function.Name}}. -func (r *{{$rel.LocalTable.NameGo}}Loaded) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { +func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } {{- if .ForeignColumnNullable}} @@ -21,13 +21,13 @@ func (r *{{$rel.LocalTable.NameGo}}Loaded) Add{{$rel.Function.Name}}(exec boil.E // {{$table.Name | singular}} replacing them completely with the passed // in related items, optionally inserting them as new records. // Replaces R.{{$rel.Function.Name}} with related. -func (r *{{$rel.LocalTable.NameGo}}Loaded) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { +func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } // Remove{{$rel.Function.Name}} relationships from objects passed in. // Removes related items from R.{{$rel.Function.Name}}. -func (r *{{$rel.LocalTable.NameGo}}Loaded) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error { +func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } {{end -}} diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 21b54b1..83af531 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -3,20 +3,21 @@ // Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item. // Sets R.{{.Function.Name}} to related. -func (r *{{.LocalTable.NameGo}}Loaded) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { - r.{{.Function.Name}} = related - r.{{.Function.Name}}.{{.Function.ForeignAssignment}} = 5 - if insert { - return related.Insert() - } +func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { + //{{.Function.Receiver}}.R.{{.Function.Name}} = related + //{{.Function.Receiver}}.R.{{.Function.Name}}.{{.Function.ForeignAssignment}} = {{.Function.Receiver}}.{{.Function.LocalAssignment}} + //if insert { +// return related.Insert() +// } - return exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, 5) +// return exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, 5) +return nil } {{- if .ForeignKey.Nullable}} // Remove{{.Function.Name}} relationship. // Sets R.{{.Function.Name}} to nil. -func (r *{{.LocalTable.NameGo}}Loaded) Remove{{.Function.Name}}(exec boil.Executor) error { +func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor) error { return nil } {{end -}} diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index aff1d1c..5092c38 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -1,6 +1,55 @@ {{- define "relationship_to_one_setops_test_helper" -}} {{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { + var err error + + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var a {{.LocalTable.NameGo}} + var b, c {{.ForeignTable.NameGo}} + + if err := a.Insert(tx); err != nil { + t.Fatal(err) + } + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &b, {{$varNameSingular}}DBTypes, false); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, {{$varNameSingular}}DBTypes, false); err != nil { + t.Fatal(err) + } + + if err = b.Insert(tx); err != nil { + t.Fatal(err) + } + + for i, x := range []*{{.ForeignTable.NameGo}}{&b, &c} { + t.Log(i) + err = a.Set{{.Function.Name}}(tx, i != 0, x) + if err != nil { + t.Fatal(err) + } + + if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}) + } + if a.R.{{.Function.Name}} != x { + t.Error("relationship struct not set to correct value") + } + + zero := reflect.Zero(reflect.TypeOf(a.{{.Function.LocalAssignment}})) + reflect.ValueOf(a.{{.Function.LocalAssignment}}).Set(zero) + + if err = a.Reload(tx); err != nil { + t.Fatal("failed to reload", err) + } + + if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}) + } + } } {{- if .ForeignKey.Nullable}} diff --git a/templates_test/singleton/boil_suites_test.tpl b/templates_test/singleton/boil_suites_test.tpl index 19900ec..467651a 100644 --- a/templates_test/singleton/boil_suites_test.tpl +++ b/templates_test/singleton/boil_suites_test.tpl @@ -136,8 +136,23 @@ func TestInsert(t *testing.T) { {{- end -}} } +// TestToOne tests cannot be run in parallel +// or deadlocks can occur. +func TestToOne(t *testing.T) { + {{- $dot := . -}} +{{- range $index, $table := .Tables}} + {{- if $table.IsJoinTable -}} + {{- else -}} + {{- range $table.FKeys -}} + {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $table . -}} + t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOne{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}) + {{end -}}{{- /* fkey range */ -}} + {{- end -}}{{- /* if join table */ -}} +{{- end -}}{{- /* tables range */ -}} +} + // TestToMany tests cannot be run in parallel -// or postgres deadlocks will occur. +// or deadlocks can occur. func TestToMany(t *testing.T) { {{- $dot := .}} {{- range $index, $table := .Tables}} @@ -157,21 +172,105 @@ func TestToMany(t *testing.T) { {{- end -}}{{- /* outer tables range */ -}} } -// TestToOne tests cannot be run in parallel -// or postgres deadlocks will occur. -func TestToOne(t *testing.T) { +// TestToOneSet tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneSet(t *testing.T) { {{- $dot := . -}} {{- range $index, $table := .Tables}} {{- if $table.IsJoinTable -}} {{- else -}} {{- range $table.FKeys -}} {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $table . -}} - t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOne{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}) + t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOneSetOp{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}) {{end -}}{{- /* fkey range */ -}} {{- end -}}{{- /* if join table */ -}} {{- end -}}{{- /* tables range */ -}} } +// TestToOneRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToOneRemove(t *testing.T) { + {{- $dot := . -}} +{{- range $index, $table := .Tables}} + {{- if $table.IsJoinTable -}} + {{- else -}} + {{- range $table.FKeys -}} + {{- $rel := textsFromForeignKey $dot.PkgName $dot.Tables $table . -}} + {{- if $rel.ForeignKey.Nullable -}} + t.Run("{{$rel.LocalTable.NameGo}}To{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToOneRemoveOp{{$rel.ForeignTable.NameGo}}_{{$rel.Function.Name}}) + {{end -}}{{- /* if foreign key nullable */ -}} + {{- end -}}{{- /* fkey range */ -}} + {{- end -}}{{- /* if join table */ -}} +{{- end -}}{{- /* tables range */ -}} +} + +// TestToManyAdd tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyAdd(t *testing.T) { + {{- $dot := .}} + {{- range $index, $table := .Tables}} + {{- $tableName := $table.Name | plural | titleCase -}} + {{- if $table.IsJoinTable -}} + {{- else -}} + {{- range $table.ToManyRelationships -}} + {{- $rel := textsFromRelationship $dot.Tables $table . -}} + {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} + {{- else -}} + t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}) + {{end -}}{{- /* if unique */ -}} + {{- end -}}{{- /* range */ -}} + {{- end -}}{{- /* outer if join table */ -}} + {{- end -}}{{- /* outer tables range */ -}} +} + +// TestToManySet tests cannot be run in parallel +// or deadlocks can occur. +func TestToManySet(t *testing.T) { + {{- $dot := .}} + {{- range $index, $table := .Tables}} + {{- $tableName := $table.Name | plural | titleCase -}} + {{- if $table.IsJoinTable -}} + {{- else -}} + {{- range $table.ToManyRelationships -}} + {{- if not .ForeignColumnNullable -}} + {{- else -}} + {{- $rel := textsFromRelationship $dot.Tables $table . -}} + {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} + {{- $oneToOne := textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table . -}} + t.Run("{{$oneToOne.LocalTable.NameGo}}OneToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}", test{{$oneToOne.LocalTable.NameGo}}ToOneSetOp{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}) + {{else -}} + t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}) + {{end -}}{{- /* if unique */ -}} + {{- end -}}{{- /* if foreign column nullable */ -}} + {{- end -}}{{- /* range */ -}} + {{- end -}}{{- /* outer if join table */ -}} + {{- end -}}{{- /* outer tables range */ -}} +} + +// TestToManyRemove tests cannot be run in parallel +// or deadlocks can occur. +func TestToManyRemove(t *testing.T) { + {{- $dot := .}} + {{- range $index, $table := .Tables}} + {{- $tableName := $table.Name | plural | titleCase -}} + {{- if $table.IsJoinTable -}} + {{- else -}} + {{- range $table.ToManyRelationships -}} + {{- if not .ForeignColumnNullable -}} + {{- else -}} + {{- $rel := textsFromRelationship $dot.Tables $table . -}} + {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} + {{- $oneToOne := textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table . -}} + t.Run("{{$oneToOne.LocalTable.NameGo}}OneToOne{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}", test{{$oneToOne.LocalTable.NameGo}}ToOneRemoveOp{{$oneToOne.ForeignTable.NameGo}}_{{$oneToOne.Function.Name}}) + {{else -}} + t.Run("{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}", test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}}) + {{end -}}{{- /* if unique */ -}} + {{- end -}}{{- /* if foreign column nullable */ -}} + {{- end -}}{{- /* range */ -}} + {{- end -}}{{- /* outer if join table */ -}} + {{- end -}}{{- /* outer tables range */ -}} +} + func TestReload(t *testing.T) { {{- range $index, $table := .Tables}} {{- if $table.IsJoinTable -}} From 9db43ae3ad53d63e82b04cb6bdb1f2fa6dbd72e2 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 22:43:50 -0700 Subject: [PATCH 05/25] Finish ToOneSet --- templates/relationship_to_one_setops.tpl | 28 ++++++++++++++----- templates_test/relationship_to_one_setops.tpl | 13 +++++---- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 83af531..4c945ef 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -4,14 +4,28 @@ // Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item. // Sets R.{{.Function.Name}} to related. func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { - //{{.Function.Receiver}}.R.{{.Function.Name}} = related - //{{.Function.Receiver}}.R.{{.Function.Name}}.{{.Function.ForeignAssignment}} = {{.Function.Receiver}}.{{.Function.LocalAssignment}} - //if insert { -// return related.Insert() -// } + var err error + if insert { + if err = related.Insert(exec); err != nil { + return err + } + } -// return exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, 5) -return nil + _, err = exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, related.{{.Function.ForeignAssignment}}) + if err != nil { + return err + } + + if {{.Function.Receiver}}.R == nil { + {{.Function.Receiver}}.R = &{{.LocalTable.NameGo}}R{ + {{.Function.Name}}: related, + } + } else { + {{.Function.Receiver}}.R.{{.Function.Name}} = related + } + + {{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}} + return nil } {{- if .ForeignKey.Nullable}} diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index 5092c38..2aef29a 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -9,11 +9,10 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na var a {{.LocalTable.NameGo}} var b, c {{.ForeignTable.NameGo}} - if err := a.Insert(tx); err != nil { + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{.ForeignKey.Table | singular | camelCase}}DBTypes, false); err != nil { t.Fatal(err) } - - seed := randomize.NewSeed() if err = randomize.Struct(seed, &b, {{$varNameSingular}}DBTypes, false); err != nil { t.Fatal(err) } @@ -21,12 +20,14 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na t.Fatal(err) } + if err := a.Insert(tx); err != nil { + t.Fatal(err) + } if err = b.Insert(tx); err != nil { t.Fatal(err) } for i, x := range []*{{.ForeignTable.NameGo}}{&b, &c} { - t.Log(i) err = a.Set{{.Function.Name}}(tx, i != 0, x) if err != nil { t.Fatal(err) @@ -40,14 +41,14 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na } zero := reflect.Zero(reflect.TypeOf(a.{{.Function.LocalAssignment}})) - reflect.ValueOf(a.{{.Function.LocalAssignment}}).Set(zero) + reflect.Indirect(reflect.ValueOf(&a.{{.Function.LocalAssignment}})).Set(zero) if err = a.Reload(tx); err != nil { t.Fatal("failed to reload", err) } if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { - t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}) + t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}, x.{{.Function.ForeignAssignment}}) } } } From 204ca1291d0b2e7585a7f2caeddc33b712fda718 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 22:53:36 -0700 Subject: [PATCH 06/25] Fix randomization so it ignores pkeys --- templates_test/relationship_to_one_setops.tpl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index 2aef29a..17c620b 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -1,5 +1,6 @@ {{- define "relationship_to_one_setops_test_helper" -}} -{{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} +{{- $varNameSingular := .ForeignKey.Table | singular | camelCase -}} +{{- $foreignVarNameSingular := .ForeignKey.ForeignTable | singular | camelCase -}} func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { var err error @@ -10,13 +11,13 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na var b, c {{.ForeignTable.NameGo}} seed := randomize.NewSeed() - if err = randomize.Struct(seed, &a, {{.ForeignKey.Table | singular | camelCase}}DBTypes, false); err != nil { + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil { t.Fatal(err) } - if err = randomize.Struct(seed, &b, {{$varNameSingular}}DBTypes, false); err != nil { + if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { t.Fatal(err) } - if err = randomize.Struct(seed, &c, {{$varNameSingular}}DBTypes, false); err != nil { + if err = randomize.Struct(seed, &c, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { t.Fatal(err) } From 1887f23ca50ad0f76f74acc52e2ef7ed09ad0fd6 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Fri, 26 Aug 2016 23:36:57 -0700 Subject: [PATCH 07/25] Finish ToOneSet - Replace the custom update that actually set every model's relationship to the same thing with a real update method so we get logging & hooks --- templates/relationship_to_one_setops.tpl | 19 ++++++-- templates_test/relationship_to_one_setops.tpl | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 4c945ef..317a67f 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -11,8 +11,10 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec } } - _, err = exec.Exec(`update "{{.ForeignKey.Table}}" set "{{.ForeignKey.Column}}" = $1`, related.{{.Function.ForeignAssignment}}) - if err != nil { + oldVal := {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}} + {{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}} + if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil { + {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}} = oldVal return err } @@ -24,7 +26,9 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec {{.Function.Receiver}}.R.{{.Function.Name}} = related } - {{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}} + {{if .ForeignKey.Nullable}} + {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true + {{end -}} return nil } {{- if .ForeignKey.Nullable}} @@ -32,6 +36,15 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec // Remove{{.Function.Name}} relationship. // Sets R.{{.Function.Name}} to nil. func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor) error { + var err error + + {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = false + if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil { + {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true + return err + } + + {{.Function.Receiver}}.R.{{.Function.Name}} = nil return nil } {{end -}} diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index 17c620b..74f5404 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -56,6 +56,49 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na {{- if .ForeignKey.Nullable}} func test{{.LocalTable.NameGo}}ToOneRemoveOp{{.ForeignTable.NameGo}}_{{.Function.Name}}(t *testing.T) { + var err error + + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var a {{.LocalTable.NameGo}} + var b {{.ForeignTable.NameGo}} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + + if err = a.Insert(tx); err != nil { + t.Fatal(err) + } + + if err = a.Set{{.Function.Name}}(tx, true, &b); err != nil { + t.Fatal(err) + } + + if err = a.Remove{{.Function.Name}}(tx); err != nil { + t.Error("failed to remove relationship") + } + + count, err := a.{{.Function.Name}}(tx).Count() + if err != nil { + t.Error(err) + } + if count != 0 { + t.Error("want no relationships remaining") + } + + if a.R.{{.Function.Name}} != nil { + t.Error("R struct entry should be nil") + } + + if a.{{.ForeignKey.Column | titleCase}}.Valid { + t.Error("R struct entry should be nil") + } } {{end -}} {{- end -}} From cc5ce81b8eccad2b8bd238883ce0bfade400dd75 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 09:51:06 -0700 Subject: [PATCH 08/25] Correct subtle issue with code that was never used --- bdb/relationships.go | 2 +- bdb/relationships_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bdb/relationships.go b/bdb/relationships.go index 212a912..968fdf8 100644 --- a/bdb/relationships.go +++ b/bdb/relationships.go @@ -72,7 +72,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab col := foreignTable.GetColumn(foreignKey.Column) relationship := ToManyRelationship{ - Table: foreignKey.Table, + Table: foreignKey.ForeignTable, Column: foreignKey.ForeignColumn, Nullable: col.Nullable, Unique: col.Unique, diff --git a/bdb/relationships_test.go b/bdb/relationships_test.go index 1eecb39..228a76b 100644 --- a/bdb/relationships_test.go +++ b/bdb/relationships_test.go @@ -101,6 +101,7 @@ func TestToManyRelationships(t *testing.T) { ToJoinTable: false, }, { + Table: "pilots", Column: "id", Nullable: false, Unique: false, @@ -233,6 +234,7 @@ func TestToManyRelationshipsNull(t *testing.T) { ToJoinTable: false, }, { + Table: "pilots", Column: "id", Nullable: true, Unique: true, From ea22d3656ccd425cd35039f0d789549f9ad9cdc6 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 12:00:42 -0700 Subject: [PATCH 09/25] Add more text helpers for ToMany. --- templates/05_relationship_to_many.tpl | 4 ++-- templates_test/relationship_to_many.tpl | 4 ++-- text_helpers.go | 4 ++++ text_helpers_test.go | 6 ++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/templates/05_relationship_to_many.tpl b/templates/05_relationship_to_many.tpl index d8eae81..e886258 100644 --- a/templates/05_relationship_to_many.tpl +++ b/templates/05_relationship_to_many.tpl @@ -28,11 +28,11 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) {{$rel.Function.Na {{if .ToJoinTable -}} queryMods = append(queryMods, qm.InnerJoin(`"{{.JoinTable}}" as "{{id 1}}" on "{{id 0}}"."{{.ForeignColumn}}" = "{{id 1}}"."{{.JoinForeignColumn}}"`), - qm.Where(`"{{id 1}}"."{{.JoinLocalColumn}}"=$1`, {{.Column | titleCase | printf "%s.%s" $rel.Function.Receiver }}), + qm.Where(`"{{id 1}}"."{{.JoinLocalColumn}}"=$1`, {{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}), ) {{else -}} queryMods = append(queryMods, - qm.Where(`"{{id 0}}"."{{.ForeignColumn}}"=$1`, {{.Column | titleCase | printf "%s.%s" $rel.Function.Receiver }}), + qm.Where(`"{{id 0}}"."{{.ForeignColumn}}"=$1`, {{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}), ) {{end}} diff --git a/templates_test/relationship_to_many.tpl b/templates_test/relationship_to_many.tpl index be1148c..83164e8 100644 --- a/templates_test/relationship_to_many.tpl +++ b/templates_test/relationship_to_many.tpl @@ -41,11 +41,11 @@ func test{{$rel.LocalTable.NameGo}}ToMany{{$rel.Function.Name}}(t *testing.T) { } {{if .ToJoinTable -}} - _, err = tx.Exec(`insert into {{.JoinTable}} ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{.Column | titleCase}}, b.{{.ForeignColumn | titleCase}}) + _, err = tx.Exec(`insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{$rel.LocalTable.ColumnNameGo}}, b.{{$rel.ForeignTable.ColumnNameGo}}) if err != nil { t.Fatal(err) } - _, err = tx.Exec(`insert into {{.JoinTable}} ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{.Column | titleCase}}, c.{{.ForeignColumn | titleCase}}) + _, err = tx.Exec(`insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)`, a.{{$rel.LocalTable.ColumnNameGo}}, c.{{$rel.ForeignTable.ColumnNameGo}}) if err != nil { t.Fatal(err) } diff --git a/text_helpers.go b/text_helpers.go index 29a991b..d600f24 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -100,6 +100,7 @@ type RelationshipToManyTexts struct { LocalTable struct { NameGo string NameSingular string + ColumnNameGo string } ForeignTable struct { @@ -107,6 +108,7 @@ type RelationshipToManyTexts struct { NameSingular string NamePluralGo string NameHumanReadable string + ColumnNameGo string Slice string } @@ -125,10 +127,12 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe r := RelationshipToManyTexts{} r.LocalTable.NameSingular = strmangle.Singular(table.Name) r.LocalTable.NameGo = strmangle.TitleCase(r.LocalTable.NameSingular) + r.LocalTable.ColumnNameGo = strmangle.TitleCase(rel.Column) r.ForeignTable.NameSingular = strmangle.Singular(rel.ForeignTable) r.ForeignTable.NamePluralGo = strmangle.TitleCase(strmangle.Plural(rel.ForeignTable)) r.ForeignTable.NameGo = strmangle.TitleCase(r.ForeignTable.NameSingular) + r.ForeignTable.ColumnNameGo = strmangle.TitleCase(rel.ForeignColumn) r.ForeignTable.Slice = fmt.Sprintf("%sSlice", strmangle.TitleCase(r.ForeignTable.NameSingular)) r.ForeignTable.NameHumanReadable = strings.Replace(rel.ForeignTable, "_", " ", -1) diff --git a/text_helpers_test.go b/text_helpers_test.go index aa574ad..929930d 100644 --- a/text_helpers_test.go +++ b/text_helpers_test.go @@ -107,11 +107,13 @@ func TestTextsFromRelationship(t *testing.T) { expect := RelationshipToManyTexts{} expect.LocalTable.NameGo = "Pilot" expect.LocalTable.NameSingular = "pilot" + expect.LocalTable.ColumnNameGo = "ID" expect.ForeignTable.NameGo = "Jet" expect.ForeignTable.NameSingular = "jet" expect.ForeignTable.NamePluralGo = "Jets" expect.ForeignTable.NameHumanReadable = "jets" + expect.ForeignTable.ColumnNameGo = "PilotID" expect.ForeignTable.Slice = "JetSlice" expect.Function.Name = "Jets" @@ -127,11 +129,13 @@ func TestTextsFromRelationship(t *testing.T) { expect = RelationshipToManyTexts{} expect.LocalTable.NameGo = "Pilot" expect.LocalTable.NameSingular = "pilot" + expect.LocalTable.ColumnNameGo = "ID" expect.ForeignTable.NameGo = "License" expect.ForeignTable.NameSingular = "license" expect.ForeignTable.NamePluralGo = "Licenses" expect.ForeignTable.NameHumanReadable = "licenses" + expect.ForeignTable.ColumnNameGo = "PilotID" expect.ForeignTable.Slice = "LicenseSlice" expect.Function.Name = "Licenses" @@ -147,11 +151,13 @@ func TestTextsFromRelationship(t *testing.T) { expect = RelationshipToManyTexts{} expect.LocalTable.NameGo = "Pilot" expect.LocalTable.NameSingular = "pilot" + expect.LocalTable.ColumnNameGo = "ID" expect.ForeignTable.NameGo = "Language" expect.ForeignTable.NameSingular = "language" expect.ForeignTable.NamePluralGo = "Languages" expect.ForeignTable.NameHumanReadable = "languages" + expect.ForeignTable.ColumnNameGo = "ID" expect.ForeignTable.Slice = "LanguageSlice" expect.Function.Name = "Languages" From f6206ea6b7ee05e4231120aef4b5b16d72c5bcd0 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 12:13:15 -0700 Subject: [PATCH 10/25] Use better errors in setops --- templates/relationship_to_one_setops.tpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 317a67f..83da9dc 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -7,7 +7,7 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec var err error if insert { if err = related.Insert(exec); err != nil { - return err + return errors.Wrap(err, "failed to insert into foreign table") } } @@ -15,7 +15,7 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec {{.Function.Receiver}}.{{.Function.LocalAssignment}} = related.{{.Function.ForeignAssignment}} if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil { {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}} = oldVal - return err + return errors.Wrap(err, "failed to update local table") } if {{.Function.Receiver}}.R == nil { @@ -41,7 +41,7 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(e {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = false if err = {{.Function.Receiver}}.Update(exec, "{{.ForeignKey.Column}}"); err != nil { {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true - return err + return errors.Wrap(err, "failed to update local table") } {{.Function.Receiver}}.R.{{.Function.Name}} = nil From 84e13cf0d40121cb743a764cedde69ea95086e2f Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 12:29:49 -0700 Subject: [PATCH 11/25] Refactor function name calculations --- text_helpers.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/text_helpers.go b/text_helpers.go index d600f24..e2cfdf6 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -137,15 +137,7 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe r.ForeignTable.NameHumanReadable = strings.Replace(rel.ForeignTable, "_", " ", -1) r.Function.Receiver = strings.ToLower(table.Name[:1]) - - // Check to see if the foreign key name is the same as the local table name. - // Simple case: yes - we can name the function the same as the plural table name - // Not simple case: We have to name the function based off the foreign key and - if colName := strings.TrimSuffix(rel.ForeignColumn, "_id"); rel.ToJoinTable || r.LocalTable.NameSingular == colName { - r.Function.Name = r.ForeignTable.NamePluralGo - } else { - r.Function.Name = strmangle.TitleCase(colName) + r.ForeignTable.NamePluralGo - } + r.Function.Name = mkFunctionName(r.LocalTable.NameSingular, r.ForeignTable.NamePluralGo, rel.ForeignColumn, rel.ToJoinTable) if rel.Nullable { col := table.GetColumn(rel.Column) @@ -164,3 +156,15 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe return r } + +// mkFunctionName checks to see if the foreign key name is the same as the local table name (minus _id suffix) +// Simple case: yes - we can name the function the same as the plural table name +// Not simple case: We have to name the function based off the foreign key and the foreign table name +func mkFunctionName(localTableSingular, foreignTablePluralGo, foreignColumn string, toJoinTable bool) string { + colName := strings.TrimSuffix(foreignColumn, "_id") + if toJoinTable || localTableSingular == colName { + return foreignTablePluralGo + } + + return strmangle.TitleCase(colName) + foreignTablePluralGo +} From 6c742e29e9034ce49d1cfe5547bdb9c3e310b058 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 17:04:51 -0700 Subject: [PATCH 12/25] Fix more broken code --- bdb/drivers/mock.go | 2 +- bdb/relationships.go | 4 ++-- bdb/relationships_test.go | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bdb/drivers/mock.go b/bdb/drivers/mock.go index b4c1943..f26b4c6 100644 --- a/bdb/drivers/mock.go +++ b/bdb/drivers/mock.go @@ -59,7 +59,7 @@ func (m *MockDriver) Columns(tableName string) ([]bdb.Column, error) { func (m *MockDriver) ForeignKeyInfo(tableName string) ([]bdb.ForeignKey, error) { return map[string][]bdb.ForeignKey{ "jets": { - {Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"}, + {Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id", ForeignColumnUnique: true}, {Table: "jets", Name: "jets_airport_id_fk", Column: "airport_id", ForeignTable: "airports", ForeignColumn: "id"}, }, "licenses": { diff --git a/bdb/relationships.go b/bdb/relationships.go index 968fdf8..0df36d5 100644 --- a/bdb/relationships.go +++ b/bdb/relationships.go @@ -58,7 +58,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab if !foreignTable.IsJoinTable { col := localTable.GetColumn(foreignKey.ForeignColumn) return ToManyRelationship{ - Table: foreignKey.Table, + Table: localTable.Name, Column: foreignKey.ForeignColumn, Nullable: col.Nullable, Unique: col.Unique, @@ -72,7 +72,7 @@ func buildRelationship(localTable Table, foreignKey ForeignKey, foreignTable Tab col := foreignTable.GetColumn(foreignKey.Column) relationship := ToManyRelationship{ - Table: foreignKey.ForeignTable, + Table: localTable.Name, Column: foreignKey.ForeignColumn, Nullable: col.Nullable, Unique: col.Unique, diff --git a/bdb/relationships_test.go b/bdb/relationships_test.go index 228a76b..de994d4 100644 --- a/bdb/relationships_test.go +++ b/bdb/relationships_test.go @@ -77,6 +77,7 @@ func TestToManyRelationships(t *testing.T) { expected := []ToManyRelationship{ { + Table: "pilots", Column: "id", Nullable: false, Unique: false, @@ -89,6 +90,7 @@ func TestToManyRelationships(t *testing.T) { ToJoinTable: false, }, { + Table: "pilots", Column: "id", Nullable: false, Unique: false, @@ -210,6 +212,7 @@ func TestToManyRelationshipsNull(t *testing.T) { expected := []ToManyRelationship{ { + Table: "pilots", Column: "id", Nullable: true, Unique: true, @@ -222,6 +225,7 @@ func TestToManyRelationshipsNull(t *testing.T) { ToJoinTable: false, }, { + Table: "pilots", Column: "id", Nullable: true, Unique: true, From d778401a7bd111144c5fafb988ec97e5c58c207a Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 17:20:26 -0700 Subject: [PATCH 13/25] Another iteration on getting text helpers correct --- text_helpers.go | 15 ++++++++++----- text_helpers_test.go | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/text_helpers.go b/text_helpers.go index e2cfdf6..a5e86da 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -28,6 +28,7 @@ type RelationshipToOneTexts struct { Function struct { PackageName string Name string + ForeignName string Varname string Receiver string @@ -54,6 +55,7 @@ func textsFromForeignKey(packageName string, tables []bdb.Table, table bdb.Table r.Function.PackageName = packageName r.Function.Name = strmangle.TitleCase(strmangle.Singular(strings.TrimSuffix(fkey.Column, "_id"))) + r.Function.ForeignName = mkFunctionName(strmangle.Singular(fkey.ForeignTable), strmangle.TitleCase(strmangle.Plural(fkey.Table)), fkey.Column, false) r.Function.Varname = strmangle.CamelCase(strmangle.Singular(fkey.ForeignTable)) r.Function.Receiver = strings.ToLower(table.Name[:1]) @@ -91,6 +93,7 @@ func textsFromOneToOneRelationship(packageName string, tables []bdb.Table, table rel := textsFromForeignKey(packageName, tables, table, fkey) rel.Function.Name = strmangle.TitleCase(strmangle.Singular(toMany.ForeignTable)) + rel.Function.ForeignName = mkFunctionName(strmangle.Singular(toMany.Table), strmangle.TitleCase(strmangle.Singular(toMany.Table)), toMany.ForeignColumn, false) rel.Function.ReverseInserts = true return rel } @@ -113,8 +116,9 @@ type RelationshipToManyTexts struct { } Function struct { - Name string - Receiver string + Name string + ForeignName string + Receiver string LocalAssignment string ForeignAssignment string @@ -138,6 +142,7 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe r.Function.Receiver = strings.ToLower(table.Name[:1]) r.Function.Name = mkFunctionName(r.LocalTable.NameSingular, r.ForeignTable.NamePluralGo, rel.ForeignColumn, rel.ToJoinTable) + r.Function.ForeignName = strmangle.TitleCase(strmangle.Plural(table.Name)) if rel.Nullable { col := table.GetColumn(rel.Column) @@ -160,9 +165,9 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe // mkFunctionName checks to see if the foreign key name is the same as the local table name (minus _id suffix) // Simple case: yes - we can name the function the same as the plural table name // Not simple case: We have to name the function based off the foreign key and the foreign table name -func mkFunctionName(localTableSingular, foreignTablePluralGo, foreignColumn string, toJoinTable bool) string { - colName := strings.TrimSuffix(foreignColumn, "_id") - if toJoinTable || localTableSingular == colName { +func mkFunctionName(fkeyTableSingular, foreignTablePluralGo, fkeyColumn string, toJoinTable bool) string { + colName := strings.TrimSuffix(fkeyColumn, "_id") + if toJoinTable || fkeyTableSingular == colName { return foreignTablePluralGo } diff --git a/text_helpers_test.go b/text_helpers_test.go index 929930d..4948880 100644 --- a/text_helpers_test.go +++ b/text_helpers_test.go @@ -34,6 +34,7 @@ func TestTextsFromForeignKey(t *testing.T) { expect.Function.PackageName = "models" expect.Function.Name = "Pilot" + expect.Function.ForeignName = "Jets" expect.Function.Varname = "pilot" expect.Function.Receiver = "j" expect.Function.ReverseInserts = false @@ -59,7 +60,7 @@ func TestTextsFromOneToOneRelationship(t *testing.T) { expect := RelationshipToOneTexts{} expect.ForeignKey = bdb.ForeignKey{ - Table: "jets", + Table: "pilots", Name: "none", Column: "id", Nullable: false, @@ -82,6 +83,7 @@ func TestTextsFromOneToOneRelationship(t *testing.T) { expect.Function.PackageName = "models" expect.Function.Name = "Jet" + expect.Function.ForeignName = "Pilot" expect.Function.Varname = "jet" expect.Function.Receiver = "p" expect.Function.ReverseInserts = true @@ -117,6 +119,7 @@ func TestTextsFromRelationship(t *testing.T) { expect.ForeignTable.Slice = "JetSlice" expect.Function.Name = "Jets" + expect.Function.ForeignName = "Pilots" expect.Function.Receiver = "p" expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "PilotID.Int" @@ -139,6 +142,7 @@ func TestTextsFromRelationship(t *testing.T) { expect.ForeignTable.Slice = "LicenseSlice" expect.Function.Name = "Licenses" + expect.Function.ForeignName = "Pilots" expect.Function.Receiver = "p" expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "PilotID" @@ -161,6 +165,7 @@ func TestTextsFromRelationship(t *testing.T) { expect.ForeignTable.Slice = "LanguageSlice" expect.Function.Name = "Languages" + expect.Function.ForeignName = "Pilots" expect.Function.Receiver = "p" expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "ID" From bc711472820da6da65677a7178c3a9d6e8b33843 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 19:10:46 -0700 Subject: [PATCH 14/25] WIP - Trying to fix bugs --- templates/relationship_to_many_setops.tpl | 42 +++++++++ .../relationship_to_many_setops.tpl | 87 ++++++++++++++++++- templates_test/relationship_to_one_setops.tpl | 10 +++ 3 files changed, 138 insertions(+), 1 deletion(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index 3fe2701..394e2c0 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -13,6 +13,48 @@ // of the {{$table.Name | singular}}, optionally inserting them as new records. // Appends related to R.{{$rel.Function.Name}}. func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { + var err error + for _, rel := range related { + rel.{{$rel.Function.ForeignAssignment}} = {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} + {{if .ForeignColumnNullable -}} + rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = true + {{end -}} + if insert { + if err = rel.Insert(exec); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + if err = rel.Update(exec, "{{.ForeignColumn}}"); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + } + } + + {{if .ToJoinTable -}} + for _, rel := range related { + query := `insert into "{{.JoinTable}}" ({{.JoinLocalColumn}}, {{.JoinForeignColumn}}) values ($1, $2)` + values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}, rel.{{$rel.ForeignTable.ColumnNameGo}}} + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, query) + fmt.Fprintln(boil.DebugWriter, values) + } + + _, err = exec.Exec(query, values...) + if err != nil { + return errors.Wrap(err, "failed to insert into join table") + } + } + {{end -}} + + if {{$rel.Function.Receiver}}.R == nil { + {{$rel.Function.Receiver}}.R = &{{$rel.LocalTable.NameGo}}R{ + {{$rel.Function.Name}}: related, + } + } else { + {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = append({{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}, related...) + } + return nil } {{- if .ForeignColumnNullable}} diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index 05afbef..28dbb5d 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -3,13 +3,98 @@ {{- $dot := . -}} {{- $table := .Table -}} {{- range .Table.ToManyRelationships -}} - {{- $varNameSingular := .ForeignTable | singular | camelCase -}} + {{- $varNameSingular := .Table | singular | camelCase -}} + {{- $foreignVarNameSingular := .ForeignTable | singular | camelCase -}} {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} {{- template "relationship_to_one_setops_test_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}} {{- else -}} {{- $rel := textsFromRelationship $dot.Tables $table .}} func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing.T) { + var err error + + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var a {{$rel.LocalTable.NameGo}} + var b, c, d, e {{$rel.ForeignTable.NameGo}} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(tx); err != nil { + t.Fatal(err) + } + if err = b.Insert(tx); err != nil { + t.Fatal(err) + } + if err = c.Insert(tx); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*{{$rel.ForeignTable.NameGo}}{ + {&b, &c}, + {&d, &e}, + } + for i, x := range foreignersSplitByInsertion { + err = a.Add{{$rel.Function.Name}}(tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := foreigners[i*2] + second := foreigners[i*2] + + {{if not .ToJoinTable -}} + if a.{{$rel.Function.LocalAssignment}} != first.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, first.{{$rel.Function.ForeignAssignment}}) + } + if a.{{$rel.Function.LocalAssignment}} != second.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, second.{{$rel.Function.ForeignAssignment}}) + } + + zero := reflect.Zero(reflect.TypeOf(first.{{$rel.Function.ForeignAssignment}})) + reflect.Indirect(reflect.ValueOf(&first.{{$rel.Function.ForeignAssignment}})).Set(zero) + reflect.Indirect(reflect.ValueOf(&second.{{$rel.Function.ForeignAssignment}})).Set(zero) + + if err = first.Reload(tx); err != nil { + t.Fatal("failed to reload", err) + } + if err = second.Reload(tx); err != nil { + t.Fatal("failed to reload", err) + } + + if a.{{$rel.Function.LocalAssignment}} != first.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, first.{{$rel.Function.ForeignAssignment}}) + } + if a.{{$rel.Function.LocalAssignment}} != second.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, second.{{$rel.Function.ForeignAssignment}}) + } + {{end -}} + + if a.R.{{$rel.Function.Name}}[0] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.{{$rel.Function.Name}}[1] != second { + t.Error("relationship slice struct not set to correct value") + } + + count, err := a.{{$rel.Function.Name}}(tx).Count() + if err != nil { + t.Fatal(err) + } + if want := int64((i+1)*2); count != want { + t.Error("want", want, "got", count) + } + } } {{if .ForeignColumnNullable}} diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index 74f5404..3af9ba3 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -51,6 +51,16 @@ func test{{.LocalTable.NameGo}}ToOneSetOp{{.ForeignTable.NameGo}}_{{.Function.Na if a.{{.Function.LocalAssignment}} != x.{{.Function.ForeignAssignment}} { t.Error("foreign key was wrong value", a.{{.Function.LocalAssignment}}, x.{{.Function.ForeignAssignment}}) } + + {{if .ForeignKey.Unique -}} + if x.R.{{.Function.ForeignName}} != &a { + t.Error("failed to append to foreign relationship struct") + } + {{else -}} + if x.R.{{.Function.ForeignName}}[0] != &a { + t.Error("failed to append to foreign relationship struct") + } + {{end -}} } } {{- if .ForeignKey.Nullable}} From 7084ce6c9fa1ceeace657b2f7b11d830af4c8b79 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 19:42:25 -0700 Subject: [PATCH 15/25] ToMany now generated from self joins --- bdb/interface_test.go | 170 ++++++++++++++++++++++++++---------------- bdb/relationships.go | 4 - 2 files changed, 106 insertions(+), 68 deletions(-) diff --git a/bdb/interface_test.go b/bdb/interface_test.go index be90f18..fd3e59f 100644 --- a/bdb/interface_test.go +++ b/bdb/interface_test.go @@ -1,98 +1,140 @@ package bdb import ( - "reflect" "testing" + + "github.com/vattle/sqlboiler/strmangle" ) -type testInterface struct{} +type mockDriver struct{} -func (t testInterface) TableNames(exclude []string) ([]string, error) { - return []string{"table1", "table2"}, nil +func (m mockDriver) TranslateColumnType(c Column) Column { return c } +func (m mockDriver) UseLastInsertID() bool { return false } +func (m mockDriver) Open() error { return nil } +func (m mockDriver) Close() {} + +func (m mockDriver) TableNames(exclude []string) ([]string, error) { + tables := []string{"pilots", "jets", "airports", "licenses", "hangars", "languages", "pilot_languages"} + return strmangle.SetComplement(tables, exclude), nil } -var testCols = []Column{ - {Name: "col1", Type: "character varying"}, - {Name: "col2", Type: "character varying", Nullable: true}, +// Columns returns a list of mock columns +func (m mockDriver) Columns(tableName string) ([]Column, error) { + return map[string][]Column{ + "pilots": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "name", Type: "string", DBType: "character"}, + }, + "airports": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "size", Type: "null.Int", DBType: "integer", Nullable: true}, + }, + "jets": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "pilot_id", Type: "int", DBType: "integer", Nullable: true, Unique: true}, + {Name: "airport_id", Type: "int", DBType: "integer"}, + {Name: "name", Type: "string", DBType: "character", Nullable: false}, + {Name: "color", Type: "null.String", DBType: "character", Nullable: true}, + {Name: "uuid", Type: "string", DBType: "uuid", Nullable: true}, + {Name: "identifier", Type: "string", DBType: "uuid", Nullable: false}, + {Name: "cargo", Type: "[]byte", DBType: "bytea", Nullable: false}, + {Name: "manifest", Type: "[]byte", DBType: "bytea", Nullable: true, Unique: true}, + }, + "licenses": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "pilot_id", Type: "int", DBType: "integer"}, + }, + "hangars": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "name", Type: "string", DBType: "character", Nullable: true, Unique: true}, + {Name: "hangar_id", Type: "int", DBType: "integer", Nullable: true}, + }, + "languages": { + {Name: "id", Type: "int", DBType: "integer"}, + {Name: "language", Type: "string", DBType: "character", Nullable: false, Unique: true}, + }, + "pilot_languages": { + {Name: "pilot_id", Type: "int", DBType: "integer"}, + {Name: "language_id", Type: "int", DBType: "integer"}, + }, + }[tableName], nil } -func (t testInterface) Columns(tableName string) ([]Column, error) { - return testCols, nil +// ForeignKeyInfo returns a list of mock foreignkeys +func (m mockDriver) ForeignKeyInfo(tableName string) ([]ForeignKey, error) { + return map[string][]ForeignKey{ + "jets": { + {Table: "jets", Name: "jets_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id", ForeignColumnUnique: true}, + {Table: "jets", Name: "jets_airport_id_fk", Column: "airport_id", ForeignTable: "airports", ForeignColumn: "id"}, + }, + "licenses": { + {Table: "licenses", Name: "licenses_pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"}, + }, + "pilot_languages": { + {Table: "pilot_languages", Name: "pilot_id_fk", Column: "pilot_id", ForeignTable: "pilots", ForeignColumn: "id"}, + {Table: "pilot_languages", Name: "jet_id_fk", Column: "language_id", ForeignTable: "languages", ForeignColumn: "id"}, + }, + "hangars": { + {Table: "hangars", Name: "hangar_fk_id", Column: "hangar_id", ForeignTable: "hangars", ForeignColumn: "id"}, + }, + }[tableName], nil } -func (t testInterface) UseLastInsertID() bool { - return false +// PrimaryKeyInfo returns mock primary key info for the passed in table name +func (m mockDriver) PrimaryKeyInfo(tableName string) (*PrimaryKey, error) { + return map[string]*PrimaryKey{ + "pilots": {Name: "pilot_id_pkey", Columns: []string{"id"}}, + "airports": {Name: "airport_id_pkey", Columns: []string{"id"}}, + "jets": {Name: "jet_id_pkey", Columns: []string{"id"}}, + "licenses": {Name: "license_id_pkey", Columns: []string{"id"}}, + "hangars": {Name: "hangar_id_pkey", Columns: []string{"id"}}, + "languages": {Name: "language_id_pkey", Columns: []string{"id"}}, + "pilot_languages": {Name: "pilot_languages_pkey", Columns: []string{"pilot_id", "language_id"}}, + }[tableName], nil } -var testPkey = &PrimaryKey{Name: "pkey1", Columns: []string{"col1", "col2"}} - -func (t testInterface) PrimaryKeyInfo(tableName string) (*PrimaryKey, error) { - return testPkey, nil -} - -var testFkeys = []ForeignKey{ - { - Name: "fkey1", - Column: "col1", - ForeignTable: "table2", - ForeignColumn: "col2", - }, - { - Name: "fkey2", - Column: "col2", - ForeignTable: "table1", - ForeignColumn: "col1", - }, -} - -func (t testInterface) ForeignKeyInfo(tableName string) ([]ForeignKey, error) { - return testFkeys, nil -} - -func (t testInterface) TranslateColumnType(column Column) Column { - column.Type = "string" - return column -} - -func (t testInterface) Open() error { - return nil -} - -func (t testInterface) Close() {} - func TestTables(t *testing.T) { t.Parallel() - tables, err := Tables(testInterface{}) + tables, err := Tables(mockDriver{}) if err != nil { t.Error(err) } - if len(tables) != 2 { - t.Errorf("Expected len 2, got: %d\n", len(tables)) + if len(tables) != 7 { + t.Errorf("Expected len 7, got: %d\n", len(tables)) } - if !reflect.DeepEqual(tables[0].Columns, testCols) { - t.Errorf("Did not get expected columns, got:\n%#v\n%#v", tables[0].Columns, testCols) + pilots := GetTable(tables, "pilots") + if len(pilots.Columns) != 2 { + t.Error() + } + if pilots.ToManyRelationships[0].ForeignTable != "jets" { + t.Error("want a to many to jets") + } + if pilots.ToManyRelationships[1].ForeignTable != "licenses" { + t.Error("want a to many to languages") + } + if pilots.ToManyRelationships[2].ForeignTable != "languages" { + t.Error("want a to many to languages") } - if !tables[0].IsJoinTable || !tables[1].IsJoinTable { - t.Errorf("Expected IsJoinTable to be true") + jets := GetTable(tables, "jets") + if len(jets.ToManyRelationships) != 0 { + t.Error("want no to many relationships") } - if !reflect.DeepEqual(tables[0].PKey, testPkey) { - t.Errorf("Did not get expected PKey, got:\n#%v\n%#v", tables[0].PKey, testPkey) + languages := GetTable(tables, "pilot_languages") + if !languages.IsJoinTable { + t.Error("languages is a join table") } - if !reflect.DeepEqual(tables[0].FKeys, testFkeys) { - t.Errorf("Did not get expected Fkey, got:\n%#v\n%#v", tables[0].FKeys, testFkeys) + hangars := GetTable(tables, "hangars") + if len(hangars.ToManyRelationships) != 1 || hangars.ToManyRelationships[0].ForeignTable != "hangars" { + t.Error("want 1 to many relationships") } - - if len(tables[0].ToManyRelationships) != 1 { - t.Error("wanted a to many relationship") - } - if len(tables[1].ToManyRelationships) != 1 { - t.Error("wanted a to many relationship") + if len(hangars.FKeys) != 1 || hangars.FKeys[0].ForeignTable != "hangars" { + t.Error("want one hangar foreign key to itself") } } diff --git a/bdb/relationships.go b/bdb/relationships.go index 0df36d5..8bbcb84 100644 --- a/bdb/relationships.go +++ b/bdb/relationships.go @@ -38,10 +38,6 @@ func toManyRelationships(table Table, tables []Table) []ToManyRelationship { var relationships []ToManyRelationship for _, t := range tables { - if t.Name == table.Name { - continue - } - for _, f := range t.FKeys { if f.ForeignTable != table.Name { continue From 43ec917a572695d2b689a9d615eb49de5bc95a6b Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sat, 27 Aug 2016 23:01:26 -0700 Subject: [PATCH 16/25] Fix another bug with one_to_one --- text_helpers.go | 6 +++++- text_helpers_test.go | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/text_helpers.go b/text_helpers.go index a5e86da..9bad06e 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -55,7 +55,11 @@ func textsFromForeignKey(packageName string, tables []bdb.Table, table bdb.Table r.Function.PackageName = packageName r.Function.Name = strmangle.TitleCase(strmangle.Singular(strings.TrimSuffix(fkey.Column, "_id"))) - r.Function.ForeignName = mkFunctionName(strmangle.Singular(fkey.ForeignTable), strmangle.TitleCase(strmangle.Plural(fkey.Table)), fkey.Column, false) + plurality := strmangle.Plural + if fkey.Unique { + plurality = strmangle.Singular + } + r.Function.ForeignName = mkFunctionName(strmangle.Singular(fkey.ForeignTable), strmangle.TitleCase(plurality(fkey.Table)), fkey.Column, false) r.Function.Varname = strmangle.CamelCase(strmangle.Singular(fkey.ForeignTable)) r.Function.Receiver = strings.ToLower(table.Name[:1]) diff --git a/text_helpers_test.go b/text_helpers_test.go index 4948880..d65a716 100644 --- a/text_helpers_test.go +++ b/text_helpers_test.go @@ -34,7 +34,7 @@ func TestTextsFromForeignKey(t *testing.T) { expect.Function.PackageName = "models" expect.Function.Name = "Pilot" - expect.Function.ForeignName = "Jets" + expect.Function.ForeignName = "Jet" expect.Function.Varname = "pilot" expect.Function.Receiver = "j" expect.Function.ReverseInserts = false @@ -45,6 +45,37 @@ func TestTextsFromForeignKey(t *testing.T) { if !reflect.DeepEqual(expect, texts) { t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts)) } + + texts = textsFromForeignKey("models", tables, jets, jets.FKeys[1]) + expect = RelationshipToOneTexts{} + expect.ForeignKey = jets.FKeys[1] + + expect.LocalTable.NameGo = "Jet" + expect.LocalTable.ColumnNameGo = "AirportID" + + expect.ForeignTable.Name = "airports" + expect.ForeignTable.NameGo = "Airport" + expect.ForeignTable.NamePluralGo = "Airports" + expect.ForeignTable.ColumnName = "id" + expect.ForeignTable.ColumnNameGo = "ID" + + expect.Function.PackageName = "models" + expect.Function.Name = "Airport" + expect.Function.ForeignName = "Jets" + expect.Function.Varname = "airport" + expect.Function.Receiver = "j" + expect.Function.ReverseInserts = false + + expect.Function.LocalAssignment = "AirportID" + expect.Function.ForeignAssignment = "ID" + + if !reflect.DeepEqual(expect, texts) { + t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts)) + } + + if !reflect.DeepEqual(expect, texts) { + t.Errorf("Want:\n%s\nGot:\n%s\n", spew.Sdump(expect), spew.Sdump(texts)) + } } func TestTextsFromOneToOneRelationship(t *testing.T) { From 625dee4648c3a77894f5e6eb0e6d202619c00b75 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 00:06:33 -0700 Subject: [PATCH 17/25] Fix documentation. - Fix test failure for ToOneSet --- templates/relationship_to_many_setops.tpl | 8 +++++-- templates/relationship_to_one_setops.tpl | 26 ++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index 394e2c0..154ca33 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -11,7 +11,8 @@ // Add{{$rel.Function.Name}} adds the given related objects to the existing relationships // of the {{$table.Name | singular}}, optionally inserting them as new records. -// Appends related to R.{{$rel.Function.Name}}. +// Appends related to {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}. +// Sets related.R.{{$rel.Function.ForeignName}} appropriately. func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { var err error for _, rel := range related { @@ -62,13 +63,16 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function // Set{{$rel.Function.Name}} removes all previously related items of the // {{$table.Name | singular}} replacing them completely with the passed // in related items, optionally inserting them as new records. -// Replaces R.{{$rel.Function.Name}} with related. +// Sets {{$rel.Function.Receiver}}.R.{{$rel.Function.ForeignName}}'s {{$rel.Function.Name}} accordingly. +// Replaces {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} with related. +// Sets related.R.{{$rel.Function.ForeignName}}'s {{$rel.Function.Name}} accordingly. func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } // Remove{{$rel.Function.Name}} relationships from objects passed in. // Removes related items from R.{{$rel.Function.Name}}. +// Sets related.R.{{$rel.Function.ForeignName}} func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error { return nil } diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 83da9dc..69a6b4c 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -2,7 +2,8 @@ {{- $varNameSingular := .ForeignKey.ForeignTable | singular | camelCase}} // Set{{.Function.Name}} of the {{.ForeignKey.Table | singular}} to the related item. -// Sets R.{{.Function.Name}} to related. +// Sets {{.Function.Receiver}}.R.{{.Function.Name}} to related. +// Adds {{.Function.Receiver}} to related.R.{{.Function.ForeignName}}. func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec boil.Executor, insert bool, related *{{.ForeignTable.NameGo}}) error { var err error if insert { @@ -26,6 +27,24 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec {{.Function.Receiver}}.R.{{.Function.Name}} = related } + {{if .ForeignKey.Unique -}} + if related.R == nil { + related.R = &{{.ForeignTable.NameGo}}R{ + {{.Function.ForeignName}}: {{.Function.Receiver}}, + } + } else { + related.R.{{.Function.ForeignName}} = {{.Function.Receiver}} + } + {{else -}} + if related.R == nil { + related.R = &{{.ForeignTable.NameGo}}R{ + {{.Function.ForeignName}}: {{.LocalTable.NameGo}}Slice{{"{"}}{{.Function.Receiver}}{{"}"}}, + } + } else { + related.R.{{.Function.ForeignName}} = append(related.R.{{.Function.ForeignName}}, {{.Function.Receiver}}) + } + {{end -}} + {{if .ForeignKey.Nullable}} {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = true {{end -}} @@ -34,8 +53,9 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec {{- if .ForeignKey.Nullable}} // Remove{{.Function.Name}} relationship. -// Sets R.{{.Function.Name}} to nil. -func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor) error { +// Sets {{.Function.Receiver}}.R.{{.Function.Name}} to nil. +// Removes {{.Function.Receiver}} from all passed in related items' relationships struct. +func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor, related ...*{{.ForeignTable.NameGo}}) error { var err error {{.Function.Receiver}}.{{.LocalTable.ColumnNameGo}}.Valid = false From 947081c579e6d06b2c79642c36223b8320bf29eb Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 00:27:50 -0700 Subject: [PATCH 18/25] Rename ReverseInserts -> OneToOne - Fix bug & failing test - Skip WIP test --- templates/relationship_to_one_setops.tpl | 2 +- templates_test/relationship_to_many_setops.tpl | 1 + templates_test/relationship_to_one.tpl | 2 +- text_helpers.go | 8 ++++---- text_helpers_test.go | 6 +++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 69a6b4c..38415ab 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -27,7 +27,7 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec {{.Function.Receiver}}.R.{{.Function.Name}} = related } - {{if .ForeignKey.Unique -}} + {{if (or .ForeignKey.Unique .Function.OneToOne) -}} if related.R == nil { related.R = &{{.ForeignTable.NameGo}}R{ {{.Function.ForeignName}}: {{.Function.Receiver}}, diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index 28dbb5d..b62cdd0 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -11,6 +11,7 @@ {{- $rel := textsFromRelationship $dot.Tables $table .}} func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing.T) { + t.Skip("WIP") var err error tx := MustTx(boil.Begin()) diff --git a/templates_test/relationship_to_one.tpl b/templates_test/relationship_to_one.tpl index 8eb73dd..c19b431 100644 --- a/templates_test/relationship_to_one.tpl +++ b/templates_test/relationship_to_one.tpl @@ -12,7 +12,7 @@ func test{{.LocalTable.NameGo}}ToOne{{.ForeignTable.NameGo}}_{{.Function.Name}}( foreign.{{.ForeignKey.ForeignColumn | titleCase}}.Valid = true {{end}} - {{if not .Function.ReverseInserts -}} + {{if not .Function.OneToOne -}} if err := foreign.Insert(tx); err != nil { t.Fatal(err) } diff --git a/text_helpers.go b/text_helpers.go index 9bad06e..dee6265 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -30,9 +30,9 @@ type RelationshipToOneTexts struct { Name string ForeignName string - Varname string - Receiver string - ReverseInserts bool + Varname string + Receiver string + OneToOne bool LocalAssignment string ForeignAssignment string @@ -98,7 +98,7 @@ func textsFromOneToOneRelationship(packageName string, tables []bdb.Table, table rel := textsFromForeignKey(packageName, tables, table, fkey) rel.Function.Name = strmangle.TitleCase(strmangle.Singular(toMany.ForeignTable)) rel.Function.ForeignName = mkFunctionName(strmangle.Singular(toMany.Table), strmangle.TitleCase(strmangle.Singular(toMany.Table)), toMany.ForeignColumn, false) - rel.Function.ReverseInserts = true + rel.Function.OneToOne = true return rel } diff --git a/text_helpers_test.go b/text_helpers_test.go index d65a716..07706cd 100644 --- a/text_helpers_test.go +++ b/text_helpers_test.go @@ -37,7 +37,7 @@ func TestTextsFromForeignKey(t *testing.T) { expect.Function.ForeignName = "Jet" expect.Function.Varname = "pilot" expect.Function.Receiver = "j" - expect.Function.ReverseInserts = false + expect.Function.OneToOne = false expect.Function.LocalAssignment = "PilotID.Int" expect.Function.ForeignAssignment = "ID" @@ -64,7 +64,7 @@ func TestTextsFromForeignKey(t *testing.T) { expect.Function.ForeignName = "Jets" expect.Function.Varname = "airport" expect.Function.Receiver = "j" - expect.Function.ReverseInserts = false + expect.Function.OneToOne = false expect.Function.LocalAssignment = "AirportID" expect.Function.ForeignAssignment = "ID" @@ -117,7 +117,7 @@ func TestTextsFromOneToOneRelationship(t *testing.T) { expect.Function.ForeignName = "Pilot" expect.Function.Varname = "jet" expect.Function.Receiver = "p" - expect.Function.ReverseInserts = true + expect.Function.OneToOne = true expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "PilotID.Int" From 0eea12fb7525ea2e7786696b6ca313e5ee0f9917 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 15:01:37 -0700 Subject: [PATCH 19/25] Complete removal on the remove method --- templates/relationship_to_one_setops.tpl | 26 ++++++++++++++++++- templates_test/relationship_to_one_setops.tpl | 14 ++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/templates/relationship_to_one_setops.tpl b/templates/relationship_to_one_setops.tpl index 38415ab..afd24aa 100644 --- a/templates/relationship_to_one_setops.tpl +++ b/templates/relationship_to_one_setops.tpl @@ -54,7 +54,7 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Set{{.Function.Name}}(exec // Remove{{.Function.Name}} relationship. // Sets {{.Function.Receiver}}.R.{{.Function.Name}} to nil. -// Removes {{.Function.Receiver}} from all passed in related items' relationships struct. +// Removes {{.Function.Receiver}} from all passed in related items' relationships struct (Optional). func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(exec boil.Executor, related ...*{{.ForeignTable.NameGo}}) error { var err error @@ -64,6 +64,30 @@ func ({{.Function.Receiver}} *{{.LocalTable.NameGo}}) Remove{{.Function.Name}}(e return errors.Wrap(err, "failed to update local table") } + for _, rel := range related { + if rel.R == nil { + continue + } + + {{if .ForeignKey.Unique -}} + rel.R.{{.Function.ForeignName}} = nil + {{else -}} + for i, ri := range rel.R.{{.Function.ForeignName}} { + if {{.Function.Receiver}}.{{.Function.LocalAssignment}} != ri.{{.Function.LocalAssignment}} { + continue + } + + ln := len(rel.R.{{.Function.ForeignName}}) + if ln > 1 && i < ln-1 { + rel.R.{{.Function.ForeignName}}[i], rel.R.{{.Function.ForeignName}}[ln-1] = + rel.R.{{.Function.ForeignName}}[ln-1], rel.R.{{.Function.ForeignName}}[i] + } + rel.R.{{.Function.ForeignName}} = rel.R.{{.Function.ForeignName}}[:ln-1] + break + } + {{end -}} + } + {{.Function.Receiver}}.R.{{.Function.Name}} = nil return nil } diff --git a/templates_test/relationship_to_one_setops.tpl b/templates_test/relationship_to_one_setops.tpl index 3af9ba3..ab27db5 100644 --- a/templates_test/relationship_to_one_setops.tpl +++ b/templates_test/relationship_to_one_setops.tpl @@ -90,7 +90,7 @@ func test{{.LocalTable.NameGo}}ToOneRemoveOp{{.ForeignTable.NameGo}}_{{.Function t.Fatal(err) } - if err = a.Remove{{.Function.Name}}(tx); err != nil { + if err = a.Remove{{.Function.Name}}(tx, &b); err != nil { t.Error("failed to remove relationship") } @@ -106,9 +106,19 @@ func test{{.LocalTable.NameGo}}ToOneRemoveOp{{.ForeignTable.NameGo}}_{{.Function t.Error("R struct entry should be nil") } - if a.{{.ForeignKey.Column | titleCase}}.Valid { + if a.{{.LocalTable.ColumnNameGo}}.Valid { t.Error("R struct entry should be nil") } + + {{if .ForeignKey.Unique -}} + if b.R.{{.Function.ForeignName}} != nil { + t.Error("failed to remove a from b's relationships") + } + {{else -}} + if len(b.R.{{.Function.ForeignName}}) != 0 { + t.Error("failed to remove a from b's relationships") + } + {{end -}} } {{end -}} {{- end -}} From e46daf09910f0d1acb40e00b49712196f6b2ca33 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 16:01:43 -0700 Subject: [PATCH 20/25] Fix ToMany.Func.ForeignName for renamed fkeys --- text_helpers.go | 8 +++++++- text_helpers_test.go | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/text_helpers.go b/text_helpers.go index dee6265..0b0b11b 100644 --- a/text_helpers.go +++ b/text_helpers.go @@ -146,7 +146,13 @@ func textsFromRelationship(tables []bdb.Table, table bdb.Table, rel bdb.ToManyRe r.Function.Receiver = strings.ToLower(table.Name[:1]) r.Function.Name = mkFunctionName(r.LocalTable.NameSingular, r.ForeignTable.NamePluralGo, rel.ForeignColumn, rel.ToJoinTable) - r.Function.ForeignName = strmangle.TitleCase(strmangle.Plural(table.Name)) + plurality := strmangle.Singular + foreignNamingColumn := rel.ForeignColumn + if rel.ToJoinTable { + plurality = strmangle.Plural + foreignNamingColumn = rel.JoinLocalColumn + } + r.Function.ForeignName = strmangle.TitleCase(plurality(strings.TrimSuffix(foreignNamingColumn, "_id"))) if rel.Nullable { col := table.GetColumn(rel.Column) diff --git a/text_helpers_test.go b/text_helpers_test.go index 07706cd..65ab7ef 100644 --- a/text_helpers_test.go +++ b/text_helpers_test.go @@ -150,7 +150,7 @@ func TestTextsFromRelationship(t *testing.T) { expect.ForeignTable.Slice = "JetSlice" expect.Function.Name = "Jets" - expect.Function.ForeignName = "Pilots" + expect.Function.ForeignName = "Pilot" expect.Function.Receiver = "p" expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "PilotID.Int" @@ -173,7 +173,7 @@ func TestTextsFromRelationship(t *testing.T) { expect.ForeignTable.Slice = "LicenseSlice" expect.Function.Name = "Licenses" - expect.Function.ForeignName = "Pilots" + expect.Function.ForeignName = "Pilot" expect.Function.Receiver = "p" expect.Function.LocalAssignment = "ID" expect.Function.ForeignAssignment = "PilotID" From 4e70c9bb17d0aacee7569fe7f4cc85b185b365ef Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 16:36:34 -0700 Subject: [PATCH 21/25] Near completion on the ToManyAdd setop --- templates/relationship_to_many_setops.tpl | 26 +++++++++- .../relationship_to_many_setops.tpl | 47 +++++++++---------- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index 154ca33..f7ecf9b 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -24,11 +24,11 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function if err = rel.Insert(exec); err != nil { return errors.Wrap(err, "failed to insert into foreign table") } - } else { + }{{if not .ToJoinTable}} else { if err = rel.Update(exec, "{{.ForeignColumn}}"); err != nil { return errors.Wrap(err, "failed to update foreign table") } - } + }{{end -}} } {{if .ToJoinTable -}} @@ -56,6 +56,28 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = append({{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}, related...) } + {{if .ToJoinTable -}} + for _, rel := range related { + if rel.R == nil { + rel.R = &{{$rel.ForeignTable.NameGo}}R{ + {{$rel.Function.ForeignName}}: {{$rel.LocalTable.NameGo}}Slice{{"{"}}{{$rel.Function.Receiver}}{{"}"}}, + } + } else { + rel.R.{{$rel.Function.ForeignName}} = append(rel.R.{{$rel.Function.ForeignName}}, {{$rel.Function.Receiver}}) + } + } + {{else -}} + for _, rel := range related { + if rel.R == nil { + rel.R = &{{$rel.ForeignTable.NameGo}}R{ + {{$rel.Function.ForeignName}}: {{$rel.Function.Receiver}}, + } + } else { + rel.R.{{$rel.Function.ForeignName}} = {{$rel.Function.Receiver}} + } + } + {{end -}} + return nil } {{- if .ForeignColumnNullable}} diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index b62cdd0..759f3d1 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -3,15 +3,14 @@ {{- $dot := . -}} {{- $table := .Table -}} {{- range .Table.ToManyRelationships -}} - {{- $varNameSingular := .Table | singular | camelCase -}} - {{- $foreignVarNameSingular := .ForeignTable | singular | camelCase -}} {{- if (and .ForeignColumnUnique (not .ToJoinTable)) -}} {{- template "relationship_to_one_setops_test_helper" (textsFromOneToOneRelationship $dot.PkgName $dot.Tables $table .) -}} {{- else -}} + {{- $varNameSingular := .Table | singular | camelCase -}} + {{- $foreignVarNameSingular := .ForeignTable | singular | camelCase -}} {{- $rel := textsFromRelationship $dot.Tables $table .}} func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing.T) { - t.Skip("WIP") var err error tx := MustTx(boil.Begin()) @@ -45,33 +44,24 @@ func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing. {&b, &c}, {&d, &e}, } + for i, x := range foreignersSplitByInsertion { err = a.Add{{$rel.Function.Name}}(tx, i != 0, x...) if err != nil { t.Fatal(err) } - first := foreigners[i*2] - second := foreigners[i*2] + first := x[0] + second := x[1] + {{- if .ToJoinTable}} - {{if not .ToJoinTable -}} - if a.{{$rel.Function.LocalAssignment}} != first.{{$rel.Function.ForeignAssignment}} { - t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, first.{{$rel.Function.ForeignAssignment}}) + if first.R.{{$rel.Function.ForeignName}}[i] != &a { + t.Error("relationship was not added properly to the slice") } - if a.{{$rel.Function.LocalAssignment}} != second.{{$rel.Function.ForeignAssignment}} { - t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, second.{{$rel.Function.ForeignAssignment}}) - } - - zero := reflect.Zero(reflect.TypeOf(first.{{$rel.Function.ForeignAssignment}})) - reflect.Indirect(reflect.ValueOf(&first.{{$rel.Function.ForeignAssignment}})).Set(zero) - reflect.Indirect(reflect.ValueOf(&second.{{$rel.Function.ForeignAssignment}})).Set(zero) - - if err = first.Reload(tx); err != nil { - t.Fatal("failed to reload", err) - } - if err = second.Reload(tx); err != nil { - t.Fatal("failed to reload", err) + if second.R.{{$rel.Function.ForeignName}}[i+1] != &a { + t.Error("relationship was not added properly to the slice") } + {{- else}} if a.{{$rel.Function.LocalAssignment}} != first.{{$rel.Function.ForeignAssignment}} { t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, first.{{$rel.Function.ForeignAssignment}}) @@ -79,13 +69,20 @@ func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing. if a.{{$rel.Function.LocalAssignment}} != second.{{$rel.Function.ForeignAssignment}} { t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, second.{{$rel.Function.ForeignAssignment}}) } - {{end -}} - if a.R.{{$rel.Function.Name}}[0] != first { + if first.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship was not added properly to the foreign slice") + } + if second.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship was not added properly to the foreign slice") + } + {{- end}} + + if a.R.{{$rel.Function.Name}}[i*2] != first { t.Error("relationship struct slice not set to correct value") } - if a.R.{{$rel.Function.Name}}[1] != second { - t.Error("relationship slice struct not set to correct value") + if a.R.{{$rel.Function.Name}}[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") } count, err := a.{{$rel.Function.Name}}(tx).Count() From 4ba184929772ec9fc0bfa28f011d4b8747e53937 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 16:51:07 -0700 Subject: [PATCH 22/25] Finish ToManyAdd --- templates/relationship_to_many_setops.tpl | 4 +++- templates_test/relationship_to_many_setops.tpl | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index f7ecf9b..6430b0e 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -16,9 +16,11 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { var err error for _, rel := range related { + {{if not .ToJoinTable -}} rel.{{$rel.Function.ForeignAssignment}} = {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} - {{if .ForeignColumnNullable -}} + {{if .ForeignColumnNullable -}} rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = true + {{end -}} {{end -}} if insert { if err = rel.Insert(exec); err != nil { diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index 759f3d1..8585491 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -55,10 +55,10 @@ func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing. second := x[1] {{- if .ToJoinTable}} - if first.R.{{$rel.Function.ForeignName}}[i] != &a { + if first.R.{{$rel.Function.ForeignName}}[0] != &a { t.Error("relationship was not added properly to the slice") } - if second.R.{{$rel.Function.ForeignName}}[i+1] != &a { + if second.R.{{$rel.Function.ForeignName}}[0] != &a { t.Error("relationship was not added properly to the slice") } {{- else}} From 128ecb4a7779beef54d53b83b06f85692d596572 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 17:07:36 -0700 Subject: [PATCH 23/25] Add ToManySet test. --- .../relationship_to_many_setops.tpl | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index 8585491..dc039ff 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -97,6 +97,110 @@ func test{{$rel.LocalTable.NameGo}}ToManyAddOp{{$rel.Function.Name}}(t *testing. {{if .ForeignColumnNullable}} func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing.T) { + var err error + + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var a {{$rel.LocalTable.NameGo}} + var b, c, d, e {{$rel.ForeignTable.NameGo}} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + } + + if err = a.Insert(tx); err != nil { + t.Fatal(err) + } + if err = b.Insert(tx); err != nil { + t.Fatal(err) + } + if err = c.Insert(tx); err != nil { + t.Fatal(err) + } + + err = a.Add{{$rel.Function.Name}}(tx, false, &b, &c) + if err != nil { + t.Fatal(err) + } + + count, err := a.{{$rel.Function.Name}}(tx).Count() + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", 2) + } + + err = a.Set{{$rel.Function.Name}}(tx, true, &d, &e) + if err != nil { + t.Fatal(err) + } + + count, err = a.{{$rel.Function.Name}}(tx).Count() + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", 2) + } + + {{- if .ToJoinTable}} + + if len(b.R.{{$rel.Function.ForeignName}}) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if len(c.R.{{$rel.Function.ForeignName}}) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if d.R.{{$rel.Function.ForeignName}}[0] != &a { + t.Error("relationship was not added properly to the slice") + } + if e.R.{{$rel.Function.ForeignName}}[0] != &a { + t.Error("relationship was not added properly to the slice") + } + {{- else}} + + if b.{{$rel.ForeignTable.ColumnNameGo}}.Valid { + t.Error("want b's foreign key value to be nil") + } + if c.{{$rel.ForeignTable.ColumnNameGo}}.Valid { + t.Error("want c's foreign key value to be nil") + } + if a.{{$rel.Function.LocalAssignment}} != d.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, d.{{$rel.Function.ForeignAssignment}}) + } + if a.{{$rel.Function.LocalAssignment}} != e.{{$rel.Function.ForeignAssignment}} { + t.Error("foreign key was wrong value", a.{{$rel.Function.LocalAssignment}}, e.{{$rel.Function.ForeignAssignment}}) + } + + if b.R.{{$rel.Function.ForeignName}} != nil { + t.Error("relationship was not removed properly from the foreign slice") + } + if c.R.{{$rel.Function.ForeignName}} != nil { + t.Error("relationship was not removed properly from the foreign slice") + } + if d.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship was not added properly to the foreign slice") + } + if e.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship was not added properly to the foreign slice") + } + {{- end}} + + if a.R.{{$rel.Function.Name}}[0] != &d { + t.Error("relationship struct slice not set to correct value") + } + if a.R.{{$rel.Function.Name}}[1] != &e { + t.Error("relationship struct slice not set to correct value") + } } func test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}}(t *testing.T) { From d9931fe7baf43d53987e559d3d631cb42a2256be Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 21:11:37 -0700 Subject: [PATCH 24/25] Finish Set --- templates/relationship_to_many_setops.tpl | 55 ++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index 6430b0e..bda27fa 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -91,7 +91,60 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Add{{$rel.Function // Replaces {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} with related. // Sets related.R.{{$rel.Function.ForeignName}}'s {{$rel.Function.Name}} accordingly. func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function.Name}}(exec boil.Executor, insert bool, related ...*{{$rel.ForeignTable.NameGo}}) error { - return nil + {{if .ToJoinTable -}} + query := `delete from "{{.JoinTable}}" where "{{.JoinLocalColumn}}" = $1` + values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}} + {{else -}} + query := `update "{{.ForeignTable}}" set "{{.ForeignColumn}}" = null where "{{.ForeignColumn}}" = $1` + values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}} + {{end -}} + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, query) + fmt.Fprintln(boil.DebugWriter, values) + } + + _, err := exec.Exec(query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + {{if .ToJoinTable -}} + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.{{$rel.Function.ForeignName}} { + if {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} != ri.{{$rel.Function.LocalAssignment}} { + continue + } + + ln := len(rel.R.{{$rel.Function.ForeignName}}) + if ln > 1 && i < ln-1 { + rel.R.{{$rel.Function.ForeignName}}[i], rel.R.{{$rel.Function.ForeignName}}[ln-1] = + rel.R.{{$rel.Function.ForeignName}}[ln-1], rel.R.{{$rel.Function.ForeignName}}[i] + } + rel.R.{{$rel.Function.ForeignName}} = rel.R.{{$rel.Function.ForeignName}}[:ln-1] + break + } + } + + {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = nil + {{else -}} + if {{$rel.Function.Receiver}}.R != nil { + for _, rel := range {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} { + rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = false + if rel.R == nil { + continue + } + + rel.R.{{$rel.Function.ForeignName}} = nil + } + + {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = nil + } + {{end -}} + + return {{$rel.Function.Receiver}}.Add{{$rel.Function.Name}}(exec, insert, related...) } // Remove{{$rel.Function.Name}} relationships from objects passed in. From ae99b2a6498d8f9f976ba50ef6b2fac669aa8cc3 Mon Sep 17 00:00:00 2001 From: Aaron L Date: Sun, 28 Aug 2016 23:26:44 -0700 Subject: [PATCH 25/25] Finish ToManyRemove. - Make Set test better. --- templates/relationship_to_many_setops.tpl | 101 ++++++++++++---- .../relationship_to_many_setops.tpl | 111 ++++++++++++++++-- 2 files changed, 183 insertions(+), 29 deletions(-) diff --git a/templates/relationship_to_many_setops.tpl b/templates/relationship_to_many_setops.tpl index bda27fa..2ee47a9 100644 --- a/templates/relationship_to_many_setops.tpl +++ b/templates/relationship_to_many_setops.tpl @@ -109,25 +109,7 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function } {{if .ToJoinTable -}} - for _, rel := range related { - if rel.R == nil { - continue - } - for i, ri := range rel.R.{{$rel.Function.ForeignName}} { - if {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} != ri.{{$rel.Function.LocalAssignment}} { - continue - } - - ln := len(rel.R.{{$rel.Function.ForeignName}}) - if ln > 1 && i < ln-1 { - rel.R.{{$rel.Function.ForeignName}}[i], rel.R.{{$rel.Function.ForeignName}}[ln-1] = - rel.R.{{$rel.Function.ForeignName}}[ln-1], rel.R.{{$rel.Function.ForeignName}}[i] - } - rel.R.{{$rel.Function.ForeignName}} = rel.R.{{$rel.Function.ForeignName}}[:ln-1] - break - } - } - + remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}}, related) {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = nil {{else -}} if {{$rel.Function.Receiver}}.R != nil { @@ -148,12 +130,87 @@ func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Set{{$rel.Function } // Remove{{$rel.Function.Name}} relationships from objects passed in. -// Removes related items from R.{{$rel.Function.Name}}. -// Sets related.R.{{$rel.Function.ForeignName}} +// Removes related items from R.{{$rel.Function.Name}} (uses pointer comparison, removal does not keep order) +// Sets related.R.{{$rel.Function.ForeignName}}. func ({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}) Remove{{$rel.Function.Name}}(exec boil.Executor, related ...*{{$rel.ForeignTable.NameGo}}) error { + var err error + {{if .ToJoinTable -}} + query := fmt.Sprintf( + `delete from "{{.JoinTable}}" where "{{.JoinLocalColumn}}" = $1 and "{{.JoinForeignColumn}}" in (%s)`, + strmangle.Placeholders(len(related), 1, 1), + ) + values := []interface{}{{"{"}}{{$rel.Function.Receiver}}.{{$rel.LocalTable.ColumnNameGo}}} + + if boil.DebugMode { + fmt.Fprintln(boil.DebugWriter, query) + fmt.Fprintln(boil.DebugWriter, values) + } + + _, err = exec.Exec(query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + {{else -}} + for _, rel := range related { + rel.{{$rel.ForeignTable.ColumnNameGo}}.Valid = false + {{if not .ToJoinTable -}} + if rel.R != nil { + rel.R.{{$rel.Function.ForeignName}} = nil + } + {{end -}} + if err = rel.Update(exec, "{{.ForeignColumn}}"); err != nil { + return err + } + } + {{end -}} + + {{if .ToJoinTable -}} + remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}}, related) + {{end -}} + if {{$rel.Function.Receiver}}.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} { + if rel != ri { + continue + } + + ln := len({{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}) + if ln > 1 && i < ln-1 { + {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[i] = {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[ln-1] + } + {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}} = {{$rel.Function.Receiver}}.R.{{$rel.Function.Name}}[:ln-1] + break + } + } + return nil } -{{end -}} + +{{if .ToJoinTable -}} +func remove{{$rel.LocalTable.NameGo}}From{{$rel.ForeignTable.NameGo}}Slice({{$rel.Function.Receiver}} *{{$rel.LocalTable.NameGo}}, related []*{{$rel.ForeignTable.NameGo}}) { + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.{{$rel.Function.ForeignName}} { + if {{$rel.Function.Receiver}}.{{$rel.Function.LocalAssignment}} != ri.{{$rel.Function.LocalAssignment}} { + continue + } + + ln := len(rel.R.{{$rel.Function.ForeignName}}) + if ln > 1 && i < ln-1 { + rel.R.{{$rel.Function.ForeignName}}[i] = rel.R.{{$rel.Function.ForeignName}}[ln-1] + } + rel.R.{{$rel.Function.ForeignName}} = rel.R.{{$rel.Function.ForeignName}}[:ln-1] + break + } + } +} +{{end -}}{{- /* if join table */ -}} +{{- end -}}{{- /* if nullable foreign key */ -}} {{- end -}}{{- /* if unique foreign key */ -}} {{- end -}}{{- /* range relationships */ -}} {{- end -}}{{- /* outer if join table */ -}} diff --git a/templates_test/relationship_to_many_setops.tpl b/templates_test/relationship_to_many_setops.tpl index dc039ff..b6fd0f7 100644 --- a/templates_test/relationship_to_many_setops.tpl +++ b/templates_test/relationship_to_many_setops.tpl @@ -126,7 +126,7 @@ func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing. t.Fatal(err) } - err = a.Add{{$rel.Function.Name}}(tx, false, &b, &c) + err = a.Set{{$rel.Function.Name}}(tx, false, &b, &c) if err != nil { t.Fatal(err) } @@ -136,7 +136,7 @@ func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing. t.Fatal(err) } if count != 2 { - t.Error("count was wrong:", 2) + t.Error("count was wrong:", count) } err = a.Set{{$rel.Function.Name}}(tx, true, &d, &e) @@ -149,7 +149,7 @@ func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing. t.Fatal(err) } if count != 2 { - t.Error("count was wrong:", 2) + t.Error("count was wrong:", count) } {{- if .ToJoinTable}} @@ -182,16 +182,16 @@ func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing. } if b.R.{{$rel.Function.ForeignName}} != nil { - t.Error("relationship was not removed properly from the foreign slice") + t.Error("relationship was not removed properly from the foreign struct") } if c.R.{{$rel.Function.ForeignName}} != nil { - t.Error("relationship was not removed properly from the foreign slice") + t.Error("relationship was not removed properly from the foreign struct") } if d.R.{{$rel.Function.ForeignName}} != &a { - t.Error("relationship was not added properly to the foreign slice") + t.Error("relationship was not added properly to the foreign struct") } if e.R.{{$rel.Function.ForeignName}} != &a { - t.Error("relationship was not added properly to the foreign slice") + t.Error("relationship was not added properly to the foreign struct") } {{- end}} @@ -204,6 +204,103 @@ func test{{$rel.LocalTable.NameGo}}ToManySetOp{{$rel.Function.Name}}(t *testing. } func test{{$rel.LocalTable.NameGo}}ToManyRemoveOp{{$rel.Function.Name}}(t *testing.T) { + var err error + + tx := MustTx(boil.Begin()) + defer tx.Rollback() + + var a {{$rel.LocalTable.NameGo}} + var b, c, d, e {{$rel.ForeignTable.NameGo}} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, {{$varNameSingular}}DBTypes, false, {{$varNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + foreigners := []*{{$rel.ForeignTable.NameGo}}{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, {{$foreignVarNameSingular}}DBTypes, false, {{$foreignVarNameSingular}}PrimaryKeyColumns...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(tx); err != nil { + t.Fatal(err) + } + + err = a.Add{{$rel.Function.Name}}(tx, true, foreigners...) + if err != nil { + t.Fatal(err) + } + + count, err := a.{{$rel.Function.Name}}(tx).Count() + if err != nil { + t.Fatal(err) + } + if count != 4 { + t.Error("count was wrong:", count) + } + + err = a.Remove{{$rel.Function.Name}}(tx, foreigners[:2]...) + if err != nil { + t.Fatal(err) + } + + count, err = a.{{$rel.Function.Name}}(tx).Count() + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + {{- if .ToJoinTable}} + + if len(b.R.{{$rel.Function.ForeignName}}) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if len(c.R.{{$rel.Function.ForeignName}}) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if d.R.{{$rel.Function.ForeignName}}[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + if e.R.{{$rel.Function.ForeignName}}[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + {{- else}} + + if b.{{$rel.ForeignTable.ColumnNameGo}}.Valid { + t.Error("want b's foreign key value to be nil") + } + if c.{{$rel.ForeignTable.ColumnNameGo}}.Valid { + t.Error("want c's foreign key value to be nil") + } + + if b.R.{{$rel.Function.ForeignName}} != nil { + t.Error("relationship was not removed properly from the foreign struct") + } + if c.R.{{$rel.Function.ForeignName}} != nil { + t.Error("relationship was not removed properly from the foreign struct") + } + if d.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship to a should have been preserved") + } + if e.R.{{$rel.Function.ForeignName}} != &a { + t.Error("relationship to a should have been preserved") + } + {{- end}} + + if len(a.R.{{$rel.Function.Name}}) != 2 { + t.Error("should have preserved two relationships") + } + + // Removal doesn't do a stable deletion for performance so we have to flip the order + if a.R.{{$rel.Function.Name}}[1] != &d { + t.Error("relationship to d should have been preserved") + } + if a.R.{{$rel.Function.Name}}[0] != &e { + t.Error("relationship to e should have been preserved") + } } {{end -}} {{- end -}}{{- /* if unique foreign key */ -}}