Skip to content

Commit a07937a

Browse files
eyo-chenEyo Chen
andauthored
Feat/update readme and example (#41)
* feat: update readme * feat: update code example * feat: add best practice * refactor: correct format --------- Co-authored-by: Eyo Chen <eyo.chen@amazingtalker.com>
1 parent 0521de6 commit a07937a

File tree

2 files changed

+186
-83
lines changed

2 files changed

+186
-83
lines changed

examples/association_test.go

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -14,106 +14,165 @@ import (
1414
"github.com/eyo-chen/gofacto/typeconv"
1515
)
1616

17-
// order has a foreign key field `CustomerID` to customer
18-
type order struct {
17+
// expense has two foreign key fields `UserID` and `CategoryID` to `User` and `Category` structs
18+
type expense struct {
1919
ID int
20-
CustomerID int `gofacto:"struct:customer"` // set the correct tag
21-
Amount int
20+
UserID int `gofacto:"foreignKey,struct:User"`
21+
CategoryID int `gofacto:"foreignKey,struct:Category,table:categories"`
2222
}
2323

24-
// Example_association demonstrates how to build associations value in an easy way.
24+
// category has a foreign key field `UserID` to `User` struct
25+
type category struct {
26+
ID int
27+
UserID int `gofacto:"foreignKey,struct:User"`
28+
}
29+
30+
type user struct {
31+
ID int
32+
}
33+
34+
// Example_association_basic demonstrates how to build basic associations value in an easy way.
2535
// First, we need to set the correct tag on the foreign key field to tell gofacto which struct to associate with.
2636
// Then, we can use `WithOne` or `WithMany` to create the association value and set the connection between the two structs.
27-
func Example_association() {
28-
// init a oreder factory
29-
f := gofacto.New(order{}).
37+
func Example_association_basic() {
38+
f := gofacto.New(category{}).
39+
WithDB(mysqlf.NewConfig(nil)) // you should pass db connection
40+
41+
// build one category with one user
42+
user1 := user{}
43+
category1, err := f.Build(ctx).
44+
WithOne(&user1). // must pass the struct pointer to WithOne or WithMany
45+
Insert()
46+
if err != nil {
47+
panic(err)
48+
}
49+
fmt.Println(category1.UserID == user1.ID) // true
50+
51+
// build two categories with two users
52+
user2 := user{}
53+
user3 := user{}
54+
categories1, err := f.BuildList(ctx, 2).
55+
WithMany([]interface{}{&user2, &user3}). // must pass the struct pointer to WithOne or WithMany
56+
Insert()
57+
if err != nil {
58+
panic(err)
59+
}
60+
fmt.Println(categories1[0].UserID == user2.ID) // true
61+
fmt.Println(categories1[1].UserID == user3.ID) // true
62+
63+
// build two categories with one user
64+
user4 := user{}
65+
categories2, err := f.BuildList(ctx, 2).
66+
WithOne(&user4). // must pass the struct pointer to WithOne or WithMany
67+
Insert()
68+
if err != nil {
69+
panic(err)
70+
}
71+
fmt.Println(categories2[0].UserID == user4.ID) // true
72+
fmt.Println(categories2[1].UserID == user4.ID) // true
73+
}
74+
75+
// Example_association_advanced demonstrates how to build advanced associations value in an easy way.
76+
// In this example, we will build the expense with user and category, and the category is associated with user.
77+
func Example_association_advanced() {
78+
f := gofacto.New(expense{}).
3079
WithDB(mysqlf.NewConfig(nil)) // you should pass db connection
3180

32-
// build one order with one customer
33-
customer1 := customer{}
34-
order1, err := f.Build(ctx).
35-
WithOne(&customer1). // must pass the struct pointer to WithOne or WithMany
81+
// build one expense with one user and one category
82+
user1 := user{}
83+
category1 := category{}
84+
expense1, err := f.Build(ctx).
85+
WithOne(&user1).
86+
WithOne(&category1).
3687
Insert()
3788
if err != nil {
3889
panic(err)
3990
}
40-
fmt.Println(order1) // {ID: 1, CustomerID: 1, Amount: {{non-zero value}}}
41-
fmt.Println(customer1) // {ID: 1, Gender: "", Name: {{non-zero value}}, Age: {{non-zero value}}}
42-
fmt.Println(order1.CustomerID == customer1.ID) // true
43-
44-
// build two orders with two customers
45-
customer2 := customer{}
46-
customer3 := customer{}
47-
orders1, err := f.BuildList(ctx, 2).
48-
WithMany([]interface{}{&customer2, &customer3}). // must pass the struct pointer to WithOne or WithMany
91+
fmt.Println(expense1.UserID == user1.ID) // true
92+
fmt.Println(expense1.CategoryID == category1.ID) // true
93+
fmt.Println(category1.UserID == user1.ID) // true
94+
// You can also use .WithOne(&user1, &category1) to pass multiple structs to WithOne
95+
96+
// build two expenses with two users and two categories
97+
user2 := user{}
98+
user3 := user{}
99+
category2 := category{}
100+
category3 := category{}
101+
expenses1, err := f.BuildList(ctx, 2).
102+
WithMany([]interface{}{&user2, &user3}). // must pass same type of structs to WithMany
103+
WithMany([]interface{}{&category2, &category3}).
49104
Insert()
50105
if err != nil {
51106
panic(err)
52107
}
53-
fmt.Println(orders1[0]) // {ID: 2, CustomerID: 2, Amount: {{non-zero value}}}
54-
fmt.Println(orders1[1]) // {ID: 3, CustomerID: 3, Amount: {{non-zero value}}}
55-
fmt.Println(customer2) // {ID: 2, Gender: "", Name: {{non-zero value}}, Age: {{non-zero value}}}
56-
fmt.Println(customer3) // {ID: 3, Gender: "", Name: {{non-zero value}}, Age: {{non-zero value}}}
57-
fmt.Println(orders1[0].CustomerID == customer2.ID) // true
58-
fmt.Println(orders1[1].CustomerID == customer3.ID) // true
59-
60-
// build two orders with one customer
61-
customer4 := customer{}
62-
orders2, err := f.BuildList(ctx, 2).
63-
WithOne(&customer4). // must pass the struct pointer to WithOne or WithMany
108+
fmt.Println(expenses1[0].UserID == user2.ID) // true
109+
fmt.Println(expenses1[1].UserID == user3.ID) // true
110+
fmt.Println(expenses1[0].CategoryID == category2.ID) // true
111+
fmt.Println(expenses1[1].CategoryID == category3.ID) // true
112+
fmt.Println(category2.UserID == user2.ID) // true
113+
fmt.Println(category3.UserID == user3.ID) // true
114+
115+
// build two expenses with one user and two categories
116+
user5 := user{}
117+
category4 := category{}
118+
category5 := category{}
119+
expenses2, err := f.BuildList(ctx, 2).
120+
WithOne(&user5).
121+
WithMany([]interface{}{&category4, &category5}).
64122
Insert()
65123
if err != nil {
66124
panic(err)
67125
}
68-
fmt.Println(orders2[0]) // {ID: 4, CustomerID: 4, Amount: {{non-zero value}}}
69-
fmt.Println(orders2[1]) // {ID: 5, CustomerID: 4, Amount: {{non-zero value}}}
70-
fmt.Println(customer4) // {ID: 4, Gender: "", Name: {{non-zero value}}, Age: {{non-zero value}}}
71-
fmt.Println(orders2[0].CustomerID == customer4.ID) // true
72-
fmt.Println(orders2[1].CustomerID == customer4.ID) // true
126+
fmt.Println(expenses2[0].UserID == user5.ID) // true
127+
fmt.Println(expenses2[1].UserID == user5.ID) // true
128+
fmt.Println(expenses2[0].CategoryID == category4.ID) // true
129+
fmt.Println(expenses2[1].CategoryID == category5.ID) // true
130+
fmt.Println(category4.UserID == user5.ID) // true
131+
fmt.Println(category5.UserID == user5.ID) // true
73132
}
74133

75-
// InsertOrders demonstrates how to use the functionality of `typeconv` package to simplify the code.
134+
// InsertCategories demonstrates how to use the functionality of `typeconv` package to simplify the code.
76135
// In some cases, we might want to wrap the insert logic into a function.
77-
// In this case, we define the `InsertOrders` function to insert `n` orders with `n` customers.
78-
func InsertOrders(ctx context.Context, f *gofacto.Factory[order], n int) ([]order, []customer, error) {
79-
// use `ToAnysWithOW` to generate `n` customers with any type
80-
// The first parameter is the number of customers to generate
136+
// In this case, we define the `InsertCategories` function to insert `n` categories with `n` users.
137+
func InsertCategories(ctx context.Context, f *gofacto.Factory[category], n int) ([]category, []user, error) {
138+
// use `ToAnysWithOW` to generate `n` users with any type
139+
// The first parameter is the number of users to generate
81140
// The second parameter is the value to override the default value(we pass nil because we don't want to override the default value)
82-
customersAny := typeconv.ToAnysWithOW[customer](n, nil)
141+
usersAny := typeconv.ToAnysWithOW[user](n, nil)
83142

84-
orders, err := f.BuildList(ctx, n).
85-
WithMany(customersAny).
143+
categories, err := f.BuildList(ctx, n).
144+
WithMany(usersAny).
86145
Insert()
87146
if err != nil {
88147
return nil, nil, err
89148
}
90149

91-
// convert the `[]any` to `[]customer` using `ToT`
92-
customers := typeconv.ToT[customer](customersAny)
150+
// convert the `[]any` to `[]user` using `ToT`
151+
users := typeconv.ToT[user](usersAny)
93152

94-
return orders, customers, nil
153+
return categories, users, nil
95154
}
96155

97-
// Without the `typeconv` package, we would need to manually convert the `[]any` to `[]customer` using `ToT`
98-
func InsertOrdersWithoutTypeconv(ctx context.Context, f *gofacto.Factory[order], n int) ([]order, []customer, error) {
99-
// manually create `n` customers with any type
100-
customersAny := make([]interface{}, n)
156+
// Without the `typeconv` package, we would need to manually convert the `[]any` to `[]user` using `ToT`
157+
func InsertCategoriesWithoutTypeconv(ctx context.Context, f *gofacto.Factory[category], n int) ([]category, []user, error) {
158+
// manually create `n` users with any type
159+
usersAny := make([]interface{}, n)
101160
for i := 0; i < n; i++ {
102-
customersAny[i] = &customer{}
161+
usersAny[i] = &user{}
103162
}
104163

105-
orders, err := f.BuildList(ctx, n).
106-
WithMany(customersAny).
164+
categories, err := f.BuildList(ctx, n).
165+
WithMany(usersAny).
107166
Insert()
108167
if err != nil {
109168
return nil, nil, err
110169
}
111170

112-
// manually convert the `[]any` to `[]customer`
113-
customers := make([]customer, n)
171+
// manually convert the `[]any` to `[]user`
172+
users := make([]user, n)
114173
for i := 0; i < n; i++ {
115-
customers[i] = *customersAny[i].(*customer)
174+
users[i] = *usersAny[i].(*user)
116175
}
117176

118-
return orders, customers, nil
177+
return categories, users, nil
119178
}

readme.md

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ gofacto is a strongly-typed and user-friendly factory library for Go, designed t
88

99
- Intuitive and straightforward usage
1010
- Strong typing and type safety
11+
- Flexible data customization
1112
- Support for various databases and ORMs
12-
- Basic association relationship support
13+
- Basic and multi-level association relationship support
1314

1415
&nbsp;
1516

@@ -210,7 +211,7 @@ order, err := factory.Build(ctx).WithOne(&c).Insert()
210211
// build two orders with two customers
211212
c1 := Customer{}
212213
c2 := Customer{}
213-
orders, err := factory.BuildList(ctx, 2).WithMany(&c1, &c2).Insert()
214+
orders, err := factory.BuildList(ctx, 2).WithMany([]interface{}{&c1, &c2}).Insert()
214215
// orders[0].CustomerID == c1.ID
215216
// orders[1].CustomerID == c2.ID
216217

@@ -221,8 +222,73 @@ orders, err := factory.BuildList(ctx, 2).WithOne(&c1).Insert()
221222
// orders[1].CustomerID == c1.ID
222223
```
223224

225+
If there are multiple level association relationships, both `WithOne` and `WithMany` methods can also come in handy.<br>
226+
Suppose we have a following schema:
227+
```go
228+
type Expense struct {
229+
ID int
230+
UserID int `gofacto:"foreignKey,struct:User"`
231+
CategoryID int `gofacto:"foreignKey,struct:Category,table:categories"`
232+
}
233+
234+
type Category struct {
235+
ID int
236+
UserID int `gofacto:"foreignKey,struct:User"`
237+
}
238+
239+
type User struct {
240+
ID int
241+
}
242+
```
243+
244+
We can build the `Expense` struct with the associated `User` and `Category` structs by using `WithOne` and `WithMany` methods.
245+
```go
246+
// build one expense with one user and one category
247+
user := User{}
248+
category := Category{}
249+
expense, err := factory.Build(ctx).WithOne(&user).WithOne(&category).Insert()
250+
// expense.UserID == user.ID
251+
// expense.CategoryID == category.ID
252+
// category.UserID == user.ID
253+
254+
// build two expenses with two users and two categories
255+
user1 := User{}
256+
user2 := User{}
257+
category1 := Category{}
258+
category2 := Category{}
259+
expenses, err := factory.BuildList(ctx, 2).WithMany([]interface{}{&user1, &user2}).WithMany([]interface{}{&category1, &category2}).Insert()
260+
// expenses[0].UserID == user1.ID
261+
// expenses[0].CategoryID == category1.ID
262+
// expenses[1].UserID == user2.ID
263+
// expenses[1].CategoryID == category2.ID
264+
// category1.UserID == user1.ID
265+
// category2.UserID == user2.ID
266+
```
267+
268+
This is one of the most powerful features of gofacto, it helps us easily build the structs with the complex associations relationships as long as setting the correct tags in the struct.<br>
269+
224270
Find out more [examples](https://github.com/eyo-chen/gofacto/blob/main/examples/association_test.go).
225271

272+
273+
<details>
274+
<summary>Best Practice to use <code>WithOne</code> & <code>WithMany</code></summary>
275+
<ul>
276+
<li>Must pass the struct pointer to <code>WithOne</code> or <code>WithMany</code></li>
277+
<li>Must pass same type of struct pointer to <code>WithMany</code></li>
278+
<li>Do not pass struct with cyclic dependency</li>
279+
</ul>
280+
281+
// Do not do this:
282+
type A struct {
283+
B_ID int `gofacto:"foreignKey,struct:B"`
284+
}
285+
type B struct {
286+
A_ID int `gofacto:"foreignKey,struct:A"`
287+
}
288+
</details>
289+
290+
291+
226292
### Reset
227293
Use `Reset` method to reset the factory.
228294
```go
@@ -403,28 +469,6 @@ type Order struct {
403469
If the struct has a custom type, gofacto will ignore the field, and leave it as zero value.<br>
404470
The clients need to set the values manually by using blueprint or overwrite if they don't want the zero value.<br>
405471

406-
3. gofacto only supports basic associations relationship.
407-
If your database schema has a complex associations relationship, you might need to set the associations manually.<br>
408-
Suppose you have a following schema:
409-
```go
410-
type Expense struct {
411-
ID int
412-
CategoryID int
413-
}
414-
415-
type Category struct {
416-
ID int
417-
UserID int
418-
}
419-
420-
type User struct {
421-
ID int
422-
}
423-
```
424-
`Expense` struct has a foreign key `CategoryID` to `Category` struct, and `Category` struct has a foreign key `UserID` to `User` struct.<br>
425-
When building `Expense` data with `WithOne(&Category{})`, `Category` struct will be built, but `User` struct will not be built. The clients need to build `User` struct manually and set `UserID` to `Category` struct.
426-
427-
428472
&nbsp;
429473

430474
# Acknowledgements

0 commit comments

Comments
 (0)