Skip to content

Commit

Permalink
add many_to_many tag association and allow model to use TableName to …
Browse files Browse the repository at this point in the history
…get table name used in query
  • Loading branch information
larrymjordan committed Jan 30, 2018
1 parent bdc7ae7 commit 3dce10b
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 2 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ type User struct {
Password string
Books Books `has_many:"books"`
FavoriteSong Song `has_one:"song" fk_id:"u_id"`
Houses Addresses `many_to_many:"users_addresses"`
}
```

Expand All @@ -289,10 +290,20 @@ type Song struct {
}
```

```go
type Address struct {
ID uuid.UUID
Street string
HouseNumber int
}

type Addresses []Address
```


```go
u := Users{}
err := tx.Eager().Where("name = 'Mark'").All(&u) // preload all associations for user with name 'Mark', i.e Books and FavoriteSong
err := tx.Eager().Where("name = 'Mark'").All(&u) // preload all associations for user with name 'Mark', i.e Books, Houses and FavoriteSong
err = tx.Eager("Books").Where("name = 'Mark'").All(&u) // preload only Books association for user with name 'Mark'.
```

Expand Down
5 changes: 5 additions & 0 deletions associations/belongs_to_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ func belongsToAssociationBuilder(p associationParams) (Association, error) {
}

func (b *belongsToAssociation) TableName() string {
m := b.ownerModel.MethodByName("TableName")
if m.IsValid() {
out := m.Call([]reflect.Value{})
return out[0].String()
}
return inflect.Tableize(b.ownerType.Name())
}

Expand Down
5 changes: 5 additions & 0 deletions associations/has_one_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func hasOneAssociationBuilder(p associationParams) (Association, error) {
}

func (h *hasOneAssociation) TableName() string {
m := h.ownedModel.MethodByName("TableName")
if m.IsValid() {
out := m.Call([]reflect.Value{})
return out[0].String()
}
return inflect.Tableize(h.ownedType.Name())
}

Expand Down
75 changes: 75 additions & 0 deletions associations/many_to_many_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package associations

import (
"fmt"
"reflect"
"strings"

"github.com/markbates/inflect"
)

type manyToManyAssociation struct {
fieldType reflect.Type
fieldValue reflect.Value
model reflect.Value
manyToManyTableName string
fkID string
}

func init() {
associationBuilders["many_to_many"] = func(p associationParams) (Association, error) {
return &manyToManyAssociation{
fieldType: p.modelValue.FieldByName(p.field.Name).Type(),
fieldValue: p.modelValue.FieldByName(p.field.Name),
model: p.modelValue,
manyToManyTableName: p.popTags.Find("many_to_many").Value,
fkID: p.popTags.Find("fk_id").Value,
}, nil
}
}

func (m *manyToManyAssociation) TableName() string {
method := m.fieldValue.MethodByName("TableName")
if method.IsValid() {
out := method.Call([]reflect.Value{})
return out[0].String()
}
return inflect.Tableize(m.fieldType.Name())
}

func (m *manyToManyAssociation) Type() reflect.Kind {
return m.fieldType.Kind()
}

func (m *manyToManyAssociation) Interface() interface{} {
if m.fieldValue.Kind() == reflect.Ptr {
val := reflect.New(m.fieldType.Elem())
m.fieldValue.Set(val)
return m.fieldValue.Interface()
}
return m.fieldValue.Addr().Interface()
}

// SQLConstraint returns the content for a where clause, and the args
// needed to execute it.
func (m *manyToManyAssociation) SQLConstraint() (string, []interface{}) {
modelColumnID := fmt.Sprintf("%s%s", strings.ToLower(m.model.Type().Name()), "_id")

var columnFieldID string
i := reflect.Indirect(m.fieldValue)
if i.Kind() == reflect.Slice {
t := i.Type().Elem()
columnFieldID = fmt.Sprintf("%s%s", strings.ToLower(t.Name()), "_id")
} else {
columnFieldID = fmt.Sprintf("%s%s", strings.ToLower(i.Type().Name()), "_id")
}

if m.fkID != "" {
columnFieldID = m.fkID
}

subQuery := fmt.Sprintf("select %s from %s where %s = ?", columnFieldID, m.manyToManyTableName, modelColumnID)
modelIDValue := m.model.FieldByName("ID").Interface()

return fmt.Sprintf("id in (%s)", subQuery), []interface{}{modelIDValue}
}
2 changes: 1 addition & 1 deletion columns/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
)

var tags = "db rw select belongs_to has_many has_one fk_id order_by"
var tags = "db rw select belongs_to has_many has_one fk_id order_by many_to_many"

// Tag represents a field tag defined exclusively for pop package.
type Tag struct {
Expand Down
28 changes: 28 additions & 0 deletions finders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@ func Test_Find_Eager_Has_One(t *testing.T) {
})
}

func Test_Find_Eager_Many_To_Many(t *testing.T) {
transaction(func(tx *pop.Connection) {
a := require.New(t)

user := User{Name: nulls.NewString("Mark")}
err := tx.Create(&user)
a.NoError(err)

address := Address{Street: "Pop Avenue", HouseNumber: 1}
err = tx.Create(&address)
a.NoError(err)

ownerProperty := UsersAddress{UserID: user.ID, AddressID: address.ID}
err = tx.Create(&ownerProperty)
a.NoError(err)

u := User{}
err = tx.Eager("Houses").Find(&u, user.ID)
a.NoError(err)

a.NotEqual(u.ID, 0)
a.Equal(u.Name.String, "Mark")

a.Equal(len(u.Houses), 1)
a.Equal(u.Houses[0].Street, address.Street)
})
}

func Test_First(t *testing.T) {
transaction(func(tx *pop.Connection) {
a := require.New(t)
Expand Down
10 changes: 10 additions & 0 deletions migrations/20160808213310_setup_tests2.up.fizz
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ create_table("songs", func(t) {
t.Column("title", "string", {})
})

create_table("addresses", func(t) {
t.Column("street", "string", {})
t.Column("house_number", "int", {})
})

create_table("users_addresses", func(t) {
t.Column("user_id", "int", {})
t.Column("address_id", "int", {})
})

{{ if eq .Dialect "postgres" }}
create_table("cakes", func(t) {
t.Column("int_slice", "int[]", {"null": true})
Expand Down
19 changes: 19 additions & 0 deletions pop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type User struct {
FullName nulls.String `db:"full_name" select:"name as full_name"`
Books Books `has_many:"books" order_by:"title asc"`
FavoriteSong Song `has_one:"song" fk_id:"u_id"`
Houses Addresses `db:"-" many_to_many:"users_addresses"`
}

type Users []User
Expand All @@ -101,6 +102,24 @@ type Book struct {

type Books []Book

type Address struct {
ID int `db:"id"`
Street string `db:"street"`
HouseNumber int `db:"house_number"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}

type Addresses []Address

type UsersAddress struct {
ID int `db:"id"`
UserID int `db:"user_id"`
AddressID int `db:"address_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}

type Friend struct {
ID int `db:"id"`
FirstName string `db:"first_name"`
Expand Down

0 comments on commit 3dce10b

Please sign in to comment.