From db6e6c64854aedb3530c2edc6d517dc5feda8f08 Mon Sep 17 00:00:00 2001 From: Jonathan Duck Date: Fri, 14 Aug 2020 16:17:57 -0700 Subject: [PATCH 1/3] Add support for embedded structs, using sqlx --- columns/columns_for_struct.go | 10 +++++----- columns/columns_test.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/columns/columns_for_struct.go b/columns/columns_for_struct.go index 22cdbebc..96c66c24 100644 --- a/columns/columns_for_struct.go +++ b/columns/columns_for_struct.go @@ -2,6 +2,8 @@ package columns import ( "reflect" + + "github.com/jmoiron/sqlx/reflectx" ) // ForStruct returns a Columns instance for @@ -31,12 +33,10 @@ func ForStructWithAlias(s interface{}, tableName string, tableAlias string) (col } } - fieldCount := st.NumField() - - for i := 0; i < fieldCount; i++ { - field := st.Field(i) + fieldMap := reflectx.NewMapper("").TypeMap(st) - popTags := TagsFor(field) + for _, i := range fieldMap.Index { + popTags := TagsFor(i.Field) tag := popTags.Find("db") if !tag.Ignored() && !tag.Empty() { diff --git a/columns/columns_test.go b/columns/columns_test.go index caa0716a..0b0558f0 100644 --- a/columns/columns_test.go +++ b/columns/columns_test.go @@ -16,6 +16,11 @@ type foo struct { WriteOnly string `db:"write" rw:"w"` } +type bar struct { + foo + MiddleName string `db:"middle_name"` +} + type foos []foo func Test_Column_MapsSlice(t *testing.T) { @@ -81,3 +86,16 @@ func Test_Columns_Sorted(t *testing.T) { r.Equal(c.String(), "amount, amount_units") r.Equal(c.QuotedString(fooQuoter{}), "`amount`, `amount_units`") } + +func Test_Columns_Embedded(t *testing.T) { + r := require.New(t) + + c := columns.ForStruct(bar{}, "bar") + + r.Equal(c.Cols["first_name"], &columns.Column{Name: "first_name", Writeable: false, Readable: true, SelectSQL: "first_name as f"}) + r.Equal(c.Cols["middle_name"], &columns.Column{Name: "middle_name", Writeable: true, Readable: true, SelectSQL: "bar.middle_name"}) + r.Equal(c.Cols["LastName"], &columns.Column{Name: "LastName", Writeable: true, Readable: true, SelectSQL: "bar.LastName"}) + r.Equal(c.Cols["read"], &columns.Column{Name: "read", Writeable: false, Readable: true, SelectSQL: "bar.read"}) + r.Equal(c.Cols["write"], &columns.Column{Name: "write", Writeable: true, Readable: false, SelectSQL: "bar.write"}) + r.Equal(c.Cols["foo"], &columns.Column{Name: "foo", Writeable: true, Readable: true, SelectSQL: "bar.foo"}) +} From f7972787e8fbbe928272f7341cd723aa80a37b0a Mon Sep 17 00:00:00 2001 From: Jonathan Duck Date: Sat, 15 Aug 2020 08:19:20 -0700 Subject: [PATCH 2/3] Add inline attr on db tag --- columns/columns_for_struct.go | 21 ++++++++++++++------- columns/columns_test.go | 15 ++++++++++----- columns/tags.go | 10 ++++++++-- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/columns/columns_for_struct.go b/columns/columns_for_struct.go index 96c66c24..39fe1fa0 100644 --- a/columns/columns_for_struct.go +++ b/columns/columns_for_struct.go @@ -2,8 +2,6 @@ package columns import ( "reflect" - - "github.com/jmoiron/sqlx/reflectx" ) // ForStruct returns a Columns instance for @@ -33,12 +31,23 @@ func ForStructWithAlias(s interface{}, tableName string, tableAlias string) (col } } - fieldMap := reflectx.NewMapper("").TypeMap(st) + appendStruct(columns, st) + + return columns +} + +func appendStruct(columns Columns, st reflect.Type) { + fieldCount := st.NumField() - for _, i := range fieldMap.Index { - popTags := TagsFor(i.Field) + for i := 0; i < fieldCount; i++ { + field := st.Field(i) + + popTags := TagsFor(field) tag := popTags.Find("db") + if tag.Attrs["inline"] { + appendStruct(columns, field.Type) + } if !tag.Ignored() && !tag.Empty() { col := tag.Value @@ -58,6 +67,4 @@ func ForStructWithAlias(s interface{}, tableName string, tableAlias string) (col } } } - - return columns } diff --git a/columns/columns_test.go b/columns/columns_test.go index 0b0558f0..0561a45f 100644 --- a/columns/columns_test.go +++ b/columns/columns_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/gobuffalo/nulls" "github.com/gobuffalo/pop/v5/columns" "github.com/stretchr/testify/require" ) @@ -16,11 +17,6 @@ type foo struct { WriteOnly string `db:"write" rw:"w"` } -type bar struct { - foo - MiddleName string `db:"middle_name"` -} - type foos []foo func Test_Column_MapsSlice(t *testing.T) { @@ -87,6 +83,12 @@ func Test_Columns_Sorted(t *testing.T) { r.Equal(c.QuotedString(fooQuoter{}), "`amount`, `amount_units`") } +type bar struct { + foo `db:"foo,inline"` + MiddleName string `db:"middle_name"` + Bio nulls.String `db:"bio"` +} + func Test_Columns_Embedded(t *testing.T) { r := require.New(t) @@ -98,4 +100,7 @@ func Test_Columns_Embedded(t *testing.T) { r.Equal(c.Cols["read"], &columns.Column{Name: "read", Writeable: false, Readable: true, SelectSQL: "bar.read"}) r.Equal(c.Cols["write"], &columns.Column{Name: "write", Writeable: true, Readable: false, SelectSQL: "bar.write"}) r.Equal(c.Cols["foo"], &columns.Column{Name: "foo", Writeable: true, Readable: true, SelectSQL: "bar.foo"}) + // Ensure it's not including non-inlined structs (nulls.String) + r.NotContains(c.Cols, "String") + r.NotContains(c.Cols, "Valid") } diff --git a/columns/tags.go b/columns/tags.go index b3f37385..d3c5e7ae 100644 --- a/columns/tags.go +++ b/columns/tags.go @@ -11,6 +11,7 @@ var tags = "db rw select belongs_to has_many has_one fk_id primary_id order_by m type Tag struct { Value string Name string + Attrs map[string]bool } // Empty validates if this pop tag is empty. @@ -42,14 +43,19 @@ func (t Tags) Find(name string) Tag { // in model field. func TagsFor(field reflect.StructField) Tags { pTags := Tags{} + attrs := map[string]bool{} for _, tag := range strings.Fields(tags) { if valTag := field.Tag.Get(tag); valTag != "" { - pTags = append(pTags, Tag{valTag, tag}) + vals := strings.Split(valTag, ",") + for _, attr := range vals[1:] { + attrs[attr] = true + } + pTags = append(pTags, Tag{vals[0], tag, attrs}) } } if len(pTags) == 0 { - pTags = append(pTags, Tag{field.Name, "db"}) + pTags = append(pTags, Tag{field.Name, "db", attrs}) } return pTags } From 36727285191a1948a498d6882fdc3979a1fc8c1d Mon Sep 17 00:00:00 2001 From: Jonathan Duck Date: Sat, 15 Aug 2020 08:23:43 -0700 Subject: [PATCH 3/3] Rename Attrs to Options Matches encoding/json and encoding/xml term --- columns/columns_for_struct.go | 2 +- columns/tags.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/columns/columns_for_struct.go b/columns/columns_for_struct.go index 39fe1fa0..47d21a21 100644 --- a/columns/columns_for_struct.go +++ b/columns/columns_for_struct.go @@ -45,7 +45,7 @@ func appendStruct(columns Columns, st reflect.Type) { popTags := TagsFor(field) tag := popTags.Find("db") - if tag.Attrs["inline"] { + if tag.Options["inline"] { appendStruct(columns, field.Type) } if !tag.Ignored() && !tag.Empty() { diff --git a/columns/tags.go b/columns/tags.go index d3c5e7ae..13e1b171 100644 --- a/columns/tags.go +++ b/columns/tags.go @@ -9,9 +9,9 @@ var tags = "db rw select belongs_to has_many has_one fk_id primary_id order_by m // Tag represents a field tag defined exclusively for pop package. type Tag struct { - Value string - Name string - Attrs map[string]bool + Value string + Name string + Options map[string]bool } // Empty validates if this pop tag is empty.