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 */ -}}