Skip to content

Commit 74a5db9

Browse files
committed
implement rel mutator
1 parent e22293e commit 74a5db9

File tree

4 files changed

+225
-18
lines changed

4 files changed

+225
-18
lines changed

changeset.go

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package changeset
33

44
import (
55
"reflect"
6+
"time"
67

78
"github.com/Fs02/changeset/params"
9+
"github.com/Fs02/rel"
810
)
911

1012
// Changeset used to cast and validate data before saving it to the database.
@@ -19,48 +21,119 @@ type Changeset struct {
1921
}
2022

2123
// Errors of changeset.
22-
func (changeset *Changeset) Errors() []error {
23-
return changeset.errors
24+
func (c Changeset) Errors() []error {
25+
return c.errors
2426
}
2527

2628
// Error of changeset, returns the first error if any.
27-
func (changeset *Changeset) Error() error {
28-
if changeset.errors != nil {
29-
return changeset.errors[0]
29+
func (c Changeset) Error() error {
30+
if c.errors != nil {
31+
return c.errors[0]
3032
}
33+
3134
return nil
3235
}
3336

3437
// Get a change from changeset.
35-
func (changeset *Changeset) Get(field string) interface{} {
36-
return changeset.changes[field]
38+
func (c Changeset) Get(field string) interface{} {
39+
return c.changes[field]
3740
}
3841

3942
// Fetch a change or value from changeset.
40-
func (changeset *Changeset) Fetch(field string) interface{} {
41-
if change, ok := changeset.changes[field]; ok {
43+
func (c Changeset) Fetch(field string) interface{} {
44+
if change, ok := c.changes[field]; ok {
4245
return change
4346
}
4447

45-
return changeset.values[field]
48+
return c.values[field]
4649
}
4750

4851
// Changes of changeset.
49-
func (changeset *Changeset) Changes() map[string]interface{} {
50-
return changeset.changes
52+
func (c Changeset) Changes() map[string]interface{} {
53+
return c.changes
5154
}
5255

5356
// Values of changeset.
54-
func (changeset *Changeset) Values() map[string]interface{} {
55-
return changeset.values
57+
func (c Changeset) Values() map[string]interface{} {
58+
return c.values
5659
}
5760

5861
// Types of changeset.
59-
func (changeset *Changeset) Types() map[string]reflect.Type {
60-
return changeset.types
62+
func (c Changeset) Types() map[string]reflect.Type {
63+
return c.types
6164
}
6265

6366
// Constraints of changeset.
64-
func (changeset *Changeset) Constraints() Constraints {
65-
return changeset.constraints
67+
func (c Changeset) Constraints() Constraints {
68+
return c.constraints
69+
}
70+
71+
// Apply mutation.
72+
func (c *Changeset) Apply(doc *rel.Document, mut *rel.Mutation) {
73+
var (
74+
pField = doc.PrimaryField()
75+
now = time.Now().Truncate(time.Second)
76+
)
77+
78+
for field, value := range c.changes {
79+
switch v := value.(type) {
80+
case *Changeset:
81+
if mut.Cascade {
82+
c.applyAssocOne(doc, field, mut, v)
83+
}
84+
case []*Changeset:
85+
if mut.Cascade {
86+
c.applyAssocMany(doc, field, mut, v)
87+
}
88+
default:
89+
if field != pField {
90+
c.set(doc, mut, field, v)
91+
}
92+
}
93+
}
94+
95+
// insert timestamp
96+
if doc.Flag(rel.HasCreatedAt) {
97+
if value, ok := doc.Value("created_at"); ok && value.(time.Time).IsZero() {
98+
c.set(doc, mut, "created_at", now)
99+
}
100+
}
101+
102+
// update timestamp
103+
if doc.Flag(rel.HasUpdatedAt) {
104+
c.set(doc, mut, "updated_at", now)
105+
}
106+
}
107+
108+
func (c *Changeset) set(doc *rel.Document, mut *rel.Mutation, field string, value interface{}) {
109+
if doc.SetValue(field, value) {
110+
mut.Add(rel.Set(field, value))
111+
}
112+
}
113+
114+
func (c *Changeset) applyAssocOne(doc *rel.Document, field string, mut *rel.Mutation, ch *Changeset) {
115+
var (
116+
assoc = doc.Association(field)
117+
assocDoc, _ = assoc.Document()
118+
assocMut = rel.Apply(assocDoc, ch)
119+
)
120+
121+
mut.SetAssoc(field, assocMut)
122+
}
123+
124+
func (c *Changeset) applyAssocMany(doc *rel.Document, field string, mut *rel.Mutation, chs []*Changeset) {
125+
var (
126+
assoc = doc.Association(field)
127+
col, _ = assoc.Collection()
128+
muts = make([]rel.Mutation, len(chs))
129+
)
130+
131+
// Reset assoc.
132+
col.Reset()
133+
134+
for i := range chs {
135+
muts[i] = rel.Apply(col.Add(), chs[i])
136+
}
137+
138+
mut.SetAssoc(field, muts...)
66139
}

changeset_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,58 @@ package changeset
22

33
import (
44
"testing"
5+
"time"
56

7+
"github.com/Fs02/changeset/params"
8+
"github.com/Fs02/rel"
69
"github.com/stretchr/testify/assert"
710
)
811

12+
type Status string
13+
14+
type User struct {
15+
ID int
16+
Name string
17+
Age int
18+
Transactions []Transaction `ref:"id" fk:"user_id"`
19+
Address Address
20+
CreatedAt time.Time
21+
UpdatedAt time.Time
22+
}
23+
24+
type Transaction struct {
25+
ID int
26+
Item string
27+
Status Status
28+
BuyerID int `db:"user_id"`
29+
Buyer User `ref:"user_id" fk:"id"`
30+
}
31+
32+
type Notes string
33+
34+
func (n Notes) Equal(other interface{}) bool {
35+
if o, ok := other.(Notes); ok {
36+
return n == o
37+
}
38+
39+
return false
40+
}
41+
42+
type Address struct {
43+
ID int
44+
UserID *int
45+
User *User
46+
Street string
47+
Notes Notes
48+
Flagged *bool
49+
DeletedAt *time.Time
50+
}
51+
52+
type Owner struct {
53+
User *User
54+
UserID *int
55+
}
56+
957
func TestChangeset(t *testing.T) {
1058
ch := Changeset{}
1159
assert.Nil(t, ch.Errors())
@@ -44,3 +92,81 @@ func TestChangeset_Fetch(t *testing.T) {
4492
assert.Equal(t, 1, len(ch.changes))
4593
assert.Equal(t, 1, len(ch.values))
4694
}
95+
96+
func TestChangesetApply(t *testing.T) {
97+
var (
98+
user User
99+
now = time.Now().Truncate(time.Second)
100+
flagged = true
101+
doc = rel.NewDocument(&user)
102+
input = params.Map{
103+
"name": "Luffy",
104+
"age": 20,
105+
"transactions": []params.Map{
106+
{
107+
"item": "Sword",
108+
"status": "pending",
109+
},
110+
{
111+
"item": "Shield",
112+
"status": "paid",
113+
},
114+
},
115+
"address": params.Map{
116+
"street": "Grove Street",
117+
"notes": "Brown fox jumps",
118+
"flagged": true,
119+
},
120+
}
121+
userMutation = rel.Apply(rel.NewDocument(&User{}),
122+
rel.Set("name", "Luffy"),
123+
rel.Set("age", 20),
124+
rel.Set("created_at", now),
125+
rel.Set("updated_at", now),
126+
)
127+
transaction1Mutation = rel.Apply(rel.NewDocument(&Transaction{}),
128+
rel.Set("item", "Sword"),
129+
rel.Set("status", Status("pending")),
130+
)
131+
transaction2Mutation = rel.Apply(rel.NewDocument(&Transaction{}),
132+
rel.Set("item", "Shield"),
133+
rel.Set("status", Status("paid")),
134+
)
135+
addressMutation = rel.Apply(rel.NewDocument(&Address{}),
136+
rel.Set("street", "Grove Street"),
137+
rel.Set("notes", Notes("Brown fox jumps")),
138+
rel.Set("flagged", true),
139+
)
140+
)
141+
142+
userMutation.SetAssoc("transactions", transaction1Mutation, transaction2Mutation)
143+
userMutation.SetAssoc("address", addressMutation)
144+
145+
ch := Cast(user, input, []string{"name", "age"})
146+
CastAssoc(ch, "transactions", func(data interface{}, input params.Params) *Changeset {
147+
ch := Cast(data, input, []string{"item", "status"})
148+
return ch
149+
})
150+
CastAssoc(ch, "address", func(data interface{}, input params.Params) *Changeset {
151+
ch := Cast(data, input, []string{"street", "notes", "flagged"})
152+
return ch
153+
})
154+
155+
assert.Nil(t, ch.Error())
156+
assert.Equal(t, userMutation, rel.Apply(doc, ch))
157+
assert.Equal(t, User{
158+
Name: "Luffy",
159+
Age: 20,
160+
CreatedAt: now,
161+
UpdatedAt: now,
162+
Transactions: []Transaction{
163+
{Item: "Sword", Status: "pending"},
164+
{Item: "Shield", Status: "paid"},
165+
},
166+
Address: Address{
167+
Street: "Grove Street",
168+
Notes: "Brown fox jumps",
169+
Flagged: &flagged,
170+
},
171+
}, user)
172+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/Fs02/changeset
33
go 1.13
44

55
require (
6+
github.com/Fs02/grimoire v1.5.0
67
github.com/Fs02/rel v0.4.0
78
github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c
89
github.com/stretchr/testify v1.5.1

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
github.com/Fs02/go-paranoid v0.0.0-20180516074805-ab50069def7d/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0=
12
github.com/Fs02/go-paranoid v0.0.0-20190122110906-018c1ac5124a/go.mod h1:mUYWV9DG75bJ33LZlW1Je3MW64017zkfUFCf+QnCJs0=
3+
github.com/Fs02/grimoire v1.5.0 h1:BnFaZ0FHzQpnzLlIGe8iOqgGeRLps33TeVGSsCFUAio=
4+
github.com/Fs02/grimoire v1.5.0/go.mod h1:Zym+jMh+rHBZxP342yxGJxQImPV0Nc6meEt5+qQY3N8=
25
github.com/Fs02/rel v0.4.0 h1:iop6aPDb2gfTIxYIbFq1sTEMJzSjlryb+mfFkSqgeig=
36
github.com/Fs02/rel v0.4.0/go.mod h1:MeJHkZO46QVfjagPSnYX/noguNDOqNK2LLGse77asxw=
47
github.com/azer/snakecase v0.0.0-20161028114325-c818dddafb5c h1:7zL0ljVI6ads5EFvx+Oq+uompnFBMJqtbuHvyobbJ1Q=
@@ -15,19 +18,23 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
1518
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
1619
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
1720
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
21+
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
1822
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
1923
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
2024
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2125
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2226
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
2327
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
2428
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
29+
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
2530
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
2631
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
2732
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
2833
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
34+
github.com/tidwall/gjson v1.1.3/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
2935
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
3036
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
37+
github.com/tidwall/match v1.0.0/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
3138
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
3239
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
3340
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=

0 commit comments

Comments
 (0)