Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow existing models in eager/flat create #199

Merged
merged 44 commits into from
Nov 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3d3f643
Reset to development
stanislas-m Oct 1, 2018
29fd8a6
Merge branch 'master' into development
stanislas-m Oct 2, 2018
ced2fb2
Query in support with slice args. (#267)
larrymjordan Oct 12, 2018
21be764
Fix some code style issues
stanislas-m Oct 13, 2018
75c221c
Trim the Readme and add SHOULDERS (#268)
stanislas-m Oct 13, 2018
e9bd038
improved cockroach configuration and fixed bugs on connection details…
sio4 Oct 16, 2018
fec0038
Should fix not being able to use existing models in eager create asso…
mclark4386 Aug 10, 2018
1f0b67a
added test to show what happens with partial update of associated object
mclark4386 Aug 10, 2018
45b970a
Should fix not being able to use existing models in eager create asso…
mclark4386 Aug 10, 2018
3cc3177
added test to show what happens with partial update of associated object
mclark4386 Aug 10, 2018
de855c6
*saving spot so I can rebase, but it's broken this commit...
mclark4386 Sep 26, 2018
3ef4f89
*fixed bug and added a debug_test function to test script if needed
mclark4386 Sep 28, 2018
3a03315
*linting and some copy paste I thought I fixed
mclark4386 Sep 29, 2018
bea4df2
*it may be done... going to improve testing to make sure it's good an…
mclark4386 Sep 30, 2018
8b9bc6b
*best to put the comment in the right place if it's going to remind m…
mclark4386 Sep 30, 2018
14eb8e0
*refactoring, bug fix, and better testing
mclark4386 Oct 1, 2018
805ed39
*remove debug logging
mclark4386 Oct 1, 2018
78fa1ab
*don't need that it would be doubling the call up since flat create h…
mclark4386 Oct 1, 2018
93b73bc
*improved docs and test coverage
mclark4386 Oct 1, 2018
7fde9d5
*should fix requested changes (cleaned up the code a bit too)
mclark4386 Oct 21, 2018
22a0e41
*shouldn't be checking empty incorrectly anymore
mclark4386 Oct 25, 2018
a4703ff
*removed some un-needed code
mclark4386 Oct 26, 2018
e641716
Reset to development
stanislas-m Oct 1, 2018
79e9531
Query in support with slice args. (#267)
larrymjordan Oct 12, 2018
d4a5a82
Fix some code style issues
stanislas-m Oct 13, 2018
ee6e319
Trim the Readme and add SHOULDERS (#268)
stanislas-m Oct 13, 2018
e30219b
improved cockroach configuration and fixed bugs on connection details…
sio4 Oct 16, 2018
b34a968
Deprecate Left and Right InnerJoins in favor of one InnerJoin. (#275)
duckbrain Oct 26, 2018
a035fdc
*should fix tests ('zero' UUIDs not getting seem as zero because they…
mclark4386 Oct 27, 2018
12a5d54
*updated update statement with more docs and to use sqlx.In
mclark4386 Oct 27, 2018
3e8181a
Merge branch 'development' into allow_existing_models_in_eager_create
mclark4386 Oct 27, 2018
4bca5f8
Improved tests
mclark4386 Oct 27, 2018
ada3461
*couple of stupid ones
mclark4386 Oct 30, 2018
7db2d6e
Merge branch 'development' into allow_existing_models_in_eager_create
mclark4386 Oct 30, 2018
7cafaae
*removed leftover import from conflict resolve merge
mclark4386 Oct 30, 2018
e4186eb
Merge branch 'development' into allow_existing_models_in_eager_create
stanislas-m Oct 30, 2018
59e7220
*should fix checking an empty UUID in eagerCreate and validateAndOnly…
mclark4386 Oct 30, 2018
3196dff
*merge development
mclark4386 Oct 31, 2018
d8c2d3f
*fix some merge mistakes and a fix
mclark4386 Oct 31, 2018
c1704fb
requested changes
mclark4386 Oct 31, 2018
501509f
Merge branch 'development' into allow_existing_models_in_eager_create
stanislas-m Nov 1, 2018
49d3c5f
Merge branch 'development' into allow_existing_models_in_eager_create
stanislas-m Nov 2, 2018
3b5c597
Merge branch 'development' into allow_existing_models_in_eager_create
stanislas-m Nov 3, 2018
0eb20f5
Fix merge issue
stanislas-m Nov 3, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions associations/association.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type AssociationBeforeCreatable interface {
type AssociationAfterCreatable interface {
AfterInterface() interface{}
AfterSetup() error
AfterProcess() AssociationStatement
Association
}

Expand All @@ -85,6 +86,11 @@ type AssociationStatement struct {
Args []interface{}
}

// Empty is true if the containing Statement is empty.
func (as AssociationStatement) Empty() bool {
return as.Statement == ""
}

// Associations a group of model associations.
type Associations []Association

Expand Down Expand Up @@ -153,3 +159,8 @@ func isZero(i interface{}) bool {
v := reflect.ValueOf(i)
return v.Interface() == reflect.Zero(v.Type()).Interface()
}

// IsZeroOfUnderlyingType will check if the value of anything is the equal to the Zero value of that type.
func IsZeroOfUnderlyingType(x interface{}) bool {
return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}
4 changes: 1 addition & 3 deletions associations/belongs_to_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ func (b *belongsToAssociation) BeforeInterface() interface{} {
return b.ownerModel.Interface()
}

currentVal := b.ownerModel.Interface()
zeroVal := reflect.Zero(b.ownerModel.Type()).Interface()
if reflect.DeepEqual(zeroVal, currentVal) {
if IsZeroOfUnderlyingType(b.ownerModel.Interface()) {
return nil
}

Expand Down
17 changes: 14 additions & 3 deletions associations/belongs_to_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import (
"reflect"
"testing"

"github.com/stretchr/testify/require"

"github.com/gobuffalo/pop/associations"

"github.com/gobuffalo/uuid"
"github.com/stretchr/testify/require"
)

type fooBelongsTo struct {
Expand Down Expand Up @@ -38,4 +36,17 @@ func Test_Belongs_To_Association(t *testing.T) {
where, args := as[0].Constraint()
a.Equal("id = ?", where)
a.Equal(id, args[0].(uuid.UUID))

bar2 := barBelongsTo{FooID: uuid.Nil}
as, err = associations.ForStruct(&bar2, "Foo")

a.NoError(err)
a.Equal(len(as), 1)
a.Equal(reflect.Struct, as[0].Kind())

before := as.AssociationsBeforeCreatable()

for index := range before {
a.Equal(nil, before[index].BeforeInterface())
}
}
49 changes: 49 additions & 0 deletions associations/has_many_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/gobuffalo/flect"
"github.com/gobuffalo/pop/nulls"
"github.com/jmoiron/sqlx"
)

// hasManyAssociation is the implementation for the has_many
Expand Down Expand Up @@ -119,3 +120,51 @@ func (a *hasManyAssociation) AfterSetup() error {
}
return nil
}

func (a *hasManyAssociation) AfterProcess() AssociationStatement {
v := a.value
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

belongingIDFieldName := "ID"

ownerIDFieldName := "ID"
ownerID := reflect.Indirect(reflect.ValueOf(a.owner)).FieldByName(ownerIDFieldName).Interface()

ids := []interface{}{}

for i := 0; i < v.Len(); i++ {
id := v.Index(i).FieldByName(belongingIDFieldName).Interface()
if !IsZeroOfUnderlyingType(id) {
ids = append(ids, id)
}
}
if len(ids) == 0 {
return AssociationStatement{
Statement: "",
Args: []interface{}{},
}
}

fk := a.fkID
if fk == "" {
fk = flect.Underscore(a.ownerName) + "_id"
}

// This will be used to update all of our owned models' foreign keys to our ID.
ret := fmt.Sprintf("UPDATE %s SET %s = ? WHERE %s in (?);", a.tableName, fk, belongingIDFieldName)

update, args, err := sqlx.In(ret, ownerID, ids)
if err != nil {
return AssociationStatement{
Statement: "",
Args: []interface{}{},
}
}

return AssociationStatement{
Statement: update,
Args: args,
}
}
60 changes: 45 additions & 15 deletions associations/has_one_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
)

type hasOneAssociation struct {
ownedModel reflect.Value
ownedType reflect.Type
ownerID interface{}
ownerName string
owner interface{}
fkID string
ownedTableName string
ownedModel reflect.Value
ownedType reflect.Type
ownerID interface{}
ownerName string
owner interface{}
fkID string
*associationSkipable
*associationComposite
}
Expand All @@ -33,12 +34,13 @@ func hasOneAssociationBuilder(p associationParams) (Association, error) {

fval := p.modelValue.FieldByName(p.field.Name)
return &hasOneAssociation{
owner: p.model,
ownedModel: fval,
ownedType: fval.Type(),
ownerID: ownerID.Interface(),
ownerName: p.modelType.Name(),
fkID: p.popTags.Find("fk_id").Value,
owner: p.model,
ownedTableName: flect.Pluralize(p.popTags.Find("has_one").Value),
ownedModel: fval,
ownedType: fval.Type(),
ownerID: ownerID.Interface(),
ownerName: p.modelType.Name(),
fkID: p.popTags.Find("fk_id").Value,
associationSkipable: &associationSkipable{
skipped: skipped,
},
Expand Down Expand Up @@ -76,9 +78,7 @@ func (h *hasOneAssociation) AfterInterface() interface{} {
return h.ownedModel.Interface()
}

currentVal := h.ownedModel.Interface()
zeroVal := reflect.Zero(h.ownedModel.Type()).Interface()
if reflect.DeepEqual(zeroVal, currentVal) {
if IsZeroOfUnderlyingType(h.ownedModel.Interface()) {
return nil
}

Expand All @@ -100,3 +100,33 @@ func (h *hasOneAssociation) AfterSetup() error {

return fmt.Errorf("could not set '%s' to '%s'", ownerID, fval)
}

func (h *hasOneAssociation) AfterProcess() AssociationStatement {
belongingIDFieldName := "ID"
id := h.ownedModel.FieldByName(belongingIDFieldName).Interface()

ownerIDFieldName := "ID"
ownerID := reflect.Indirect(reflect.ValueOf(h.owner)).FieldByName(ownerIDFieldName).Interface()

ids := []interface{}{ownerID}

if IsZeroOfUnderlyingType(id) {
return AssociationStatement{
mclark4386 marked this conversation as resolved.
Show resolved Hide resolved
Statement: "",
Args: []interface{}{},
}
}
ids = append(ids, id)

fk := h.fkID
if fk == "" {
fk = flect.Underscore(h.ownerName) + "_id"
}

ret := fmt.Sprintf("UPDATE %s SET %s = ? WHERE %s = ?", h.ownedTableName, fk, belongingIDFieldName)

return AssociationStatement{
Statement: ret,
Args: ids,
}
}
8 changes: 8 additions & 0 deletions associations/has_one_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ func Test_Has_One_Association(t *testing.T) {
where, args := as[0].Constraint()
a.Equal("foo_has_one_id = ?", where)
a.Equal(id, args[0].(uuid.UUID))

foo2 := FooHasOne{}

as, err = associations.ForStruct(&foo2)
after := as.AssociationsAfterCreatable()
for index := range after {
a.Equal(nil, after[index].AfterInterface())
}
}

func Test_Has_One_SetValue(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions connection_details_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,10 @@ func Test_ConnectionDetails_Finalize_SQLite_with_Dialect(t *testing.T) {
r.Equal("", cd.Port)
r.Equal("", cd.User)
}

func Test_ConnectionDetails_Finalize_SQLite_without_URL(t *testing.T) {
r := require.New(t)

cd := &ConnectionDetails{
Dialect: "sqlite",
Database: "./foo.db",
Expand Down
3 changes: 2 additions & 1 deletion dialect_cockroach.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
"sync"

_ "github.com/cockroachdb/cockroach-go/crdb" // Load CockroachdbQL/postgres Go driver. Also loads github.com/lib/pq
_ "github.com/cockroachdb/cockroach-go/crdb" // Load CockroachdbQL/postgres Go driver which also loads github.com/lib/pq
"github.com/gobuffalo/fizz"
"github.com/gobuffalo/fizz/translators"
"github.com/gobuffalo/pop/columns"
Expand Down Expand Up @@ -196,6 +196,7 @@ func (p *cockroach) Lock(fn func() error) error {

func (p *cockroach) DumpSchema(w io.Writer) error {
cmd := exec.Command("cockroach", "dump", p.Details().Database, "--dump-mode=schema")

c := p.ConnectionDetails
if defaults.String(c.Options["sslmode"], "disable") == "disable" || strings.Contains(c.RawOptions, "sslmode=disable") {
cmd.Args = append(cmd.Args, "--insecure")
Expand Down
2 changes: 2 additions & 0 deletions dialect_cockroach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ func Test_Cockroach_URL_Raw(t *testing.T) {
r.Equal("scheme://user:pass@host:port/database?option1=value1", m.URL())
r.Equal("postgres://user:pass@host:port/?option1=value1", m.urlWithoutDb())
}

func Test_Cockroach_URL_Build(t *testing.T) {
r := require.New(t)

cd := &ConnectionDetails{
Dialect: "cockroach",
Database: "database",
Expand Down
69 changes: 65 additions & 4 deletions executors.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package pop

import (
"fmt"
"reflect"

"github.com/gobuffalo/pop/associations"
"github.com/gobuffalo/pop/columns"
"github.com/gobuffalo/pop/logging"
"github.com/gobuffalo/uuid"
Expand Down Expand Up @@ -60,13 +61,21 @@ func (c *Connection) ValidateAndSave(model interface{}, excludeColumns ...string

var emptyUUID = uuid.Nil.String()

// IsZeroOfUnderlyingType will check if the value of anything is the equal to the Zero value of that type.
func IsZeroOfUnderlyingType(x interface{}) bool {
return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

// Save wraps the Create and Update methods. It executes a Create if no ID is provided with the entry;
// or issues an Update otherwise.
func (c *Connection) Save(model interface{}, excludeColumns ...string) error {
sm := &Model{Value: model}
return sm.iterate(func(m *Model) error {
id := m.ID()
if fmt.Sprint(id) == "0" || fmt.Sprint(id) == emptyUUID {
id, err := m.fieldByName("ID")
if err != nil {
return err
}
if IsZeroOfUnderlyingType(id.Interface()) {
return c.Create(m.Value, excludeColumns...)
}
return c.Update(m.Value, excludeColumns...)
Expand Down Expand Up @@ -101,7 +110,11 @@ func (c *Connection) Create(model interface{}, excludeColumns ...string) error {
sm := &Model{Value: model}
return sm.iterate(func(m *Model) error {
return c.timeFunc("Create", func() error {
var err error
asos, err := associations.ForStruct(m.Value)
if err != nil {
return err
}

if err = m.beforeSave(c); err != nil {
return err
}
Expand All @@ -110,6 +123,23 @@ func (c *Connection) Create(model interface{}, excludeColumns ...string) error {
return err
}

processAssoc := len(asos) > 0

if processAssoc {
before := asos.AssociationsBeforeCreatable()
for index := range before {
i := before[index].BeforeInterface()
if i == nil {
continue
}

err = before[index].BeforeSetup()
if err != nil {
return err
}
}
}

tn := m.TableName()
cols := columns.ForStructWithAlias(m.Value, tn, m.As)

Expand All @@ -124,6 +154,37 @@ func (c *Connection) Create(model interface{}, excludeColumns ...string) error {
return err
}

if processAssoc {
after := asos.AssociationsAfterCreatable()
for index := range after {
stm := after[index].AfterProcess()
if c.TX != nil && !stm.Empty() {
_, err := c.TX.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
if err != nil {
return err
}
}
}

stms := asos.AssociationsCreatableStatement()
for index := range stms {
statements := stms[index].Statements()
for _, stm := range statements {
if c.TX != nil {
_, err := c.TX.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
if err != nil {
return err
}
continue
}
_, err = c.Store.Exec(c.Dialect.TranslateSQL(stm.Statement), stm.Args...)
if err != nil {
return err
}
}
}
}

if err = m.afterCreate(c); err != nil {
return err
}
Expand Down
Loading