-
Notifications
You must be signed in to change notification settings - Fork 0
/
model_template.go
193 lines (152 loc) · 7.08 KB
/
model_template.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
MODEL TEMPLATE
How to use:
1. Copy the file.
2. Replace (case sensitive) "ModelTemplate" with your model's ProperName.
3. Replace (case sensitive) "model_template" with your model's underscored_name.
4. Modify your fields and relationships.
5. Add your validations as needed. https://github.com/go-playground/validator
6. Comment your indices for easy reference later.
7. Change the comments!
FYI: Embeddable related documents only works because of the go.mod replacement
from globalsign/mgo to Nifty255/mgo, allowing the use of "omitalways" tags.
*/
package gomodel
import (
// Import builtin packages.
"encoding/json"
"time"
// Import 3rd party packages.
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"github.com/go-redis/redis"
"github.com/rs/zerolog/log"
// Import internal packages.
"github.com/badpetbot/gocommon/net"
"github.com/badpetbot/gocommon/validation"
)
// ModelTemplateClientName is the name of the MgoDriver to use for ModelTemplate.
const ModelTemplateClientName = "main"
// ModelTemplateDBName is the name of the database to use for ModelTemplate.
const ModelTemplateDBName = "badpetbot"
// ModelTemplateColName is the name of the collection to use for ModelTemplate.
const ModelTemplateColName = "model_templates"
// ModelTemplateCol gets a collection reference for ModelTemplate.
func ModelTemplateCol() *mgo.Collection {
return net.MgoCol(ModelTemplateClientName, ModelTemplateDBName, ModelTemplateColName)
}
// INDICES:
// { _id: 1 }
// ModelTemplate is a model template, meant to be copied.
type ModelTemplate struct {
// ID is a BSON ID generated in Create.
ID bson.ObjectId `bson:"_id" json:"_id" validate:"required"`
CreatedAt time.Time `bson:"created_at" json:"created_at" validate:"required"`
UpdatedAt time.Time `bson:"updated_at" json:"updated_at" validate:"required"`
FieldWithDefault int `bson:"field_with_default" json:"field_with_default" validate:"gt=2,lt=10"`
// Relationship IDs. Referencing another document's ID causes this document to "belong to" that document. A document can
// belong to one other document, or many. A document to which this one blongs can "have" many other documents of this type.
// For a generic example, a "session" may belong to 1 user, and each user may have many sessions.
RelatedTemplateID *bson.ObjectId `bson:"related_template_id" json:"related_template_id" validate:"-"`
RelatedTemplateIDs []bson.ObjectId `bson:"related_template_ids" json:"related_template_ids" validate:"-"`
// Embeddables. Can be pulled in via aggregations, "omitalways" on the bson tag prevents storing embedded documents
// which are meant only to be related objects. Embeddable references can be declared without "belonging" IDs. This sort
// of relationship is known as "has" one/many. The child model is responsible for "belonging" to this one in that case.
RelatedTemplate *ModelTemplate `bson:"related_template,omitalways" json:"related_template" validate:"-"`
RelatedTemplates []ModelTemplate `bson:"related_templates,omitalways" json:"related_templates" validate:"-"`
}
// Create persists the document in the database. It can optionally run validations if present and
// prevent model persistence if they do not pass.
func (this *ModelTemplate) Create() error {
// Ensure ID, timestamps, and tokens.
this.ID = bson.NewObjectId()
now := time.Now()
this.CreatedAt = now
this.UpdatedAt = now
// Ensure defaults.
this.FieldWithDefault = 7
// Run validations and return if they fail.
if err := this.Validate(); err != nil {
return err
}
// Persist the ModelTemplate.
return net.MgoCol(ModelTemplateClientName, ModelTemplateDBName, ModelTemplateColName).Insert(this)
}
// Update updates the document in the database. Important note, this function does NOT prepend
// the provided updates with "$set" or any other operator.
func (this *ModelTemplate) Update(updates bson.M) error {
// Update updated-at timestamp.
this.UpdatedAt = time.Now()
_, setting := updates["$set"]
if !setting {
updates["$set"] = bson.M{}
}
updates["$set"].(bson.M)["updated_at"] = this.UpdatedAt
if err := this.Validate(); err != nil {
return err
}
// Persist the updates.
return net.MgoCol(ModelTemplateClientName, ModelTemplateDBName, ModelTemplateColName).UpdateId(this.ID, updates)
}
// Delete permanently removes the document from the database.
func (this *ModelTemplate) Delete() error {
// Delete the Link.
return net.MgoCol(ModelTemplateClientName, ModelTemplateDBName, ModelTemplateColName).RemoveId(this.ID)
}
// Validate runs validations against the model's fields.
func (this *ModelTemplate) Validate() error {
// Implement validation rules here.
return validation.NewValidator().Struct(this)
}
// Cache functions.
// CacheGetModelTemplate attempts to find a ModelTemplate by the key and value specified in cache before looking
// in the database and setting cache if found. If "negCache" is true, will check for neg-cache
// first, and also set neg-cache if the document wasn't found in the database either.
func CacheGetModelTemplate(key, value string, negCache bool) (*ModelTemplate, error) {
client := net.RedisGetClient(ModelTemplateClientName)
cacheKey := ModelTemplateClientName+":"+ModelTemplateDBName+":"+ModelTemplateColName+":"+key+":"+value
// Return not-found early if neg-cache exists.
if negCache {
if result, err := client.Get("neg:"+cacheKey).Result(); err != nil {
return nil, err
} else if result != "" {
return nil, mgo.ErrNotFound
}
}
// Return what's in cache if it's found.
if result, err := client.Get(cacheKey).Result(); err != nil {
return nil, err
} else if result != "" {
server := new(ModelTemplate)
err = json.Unmarshal([]byte(result), server)
return server, err
}
// Get what's in the database.
server := new(ModelTemplate)
err := net.MgoCol(ModelTemplateClientName, ModelTemplateDBName, ModelTemplateColName).Find(bson.M{
key: value,
}).One(server)
// If it wasn't found and negCache is true, fill neg cache.
if err == mgo.ErrNotFound && negCache {
go fillNegCacheModelTemplate(client, cacheKey)
// Else if there's no error, fill cache.
} else if err != nil {
go fillCacheModelTemplate(client, cacheKey, server)
}
return server, err
}
func fillCacheModelTemplate(client *redis.Client, key string, value *ModelTemplate) {
serialized, err := json.Marshal(value)
if err != nil {
log.Warn().AnErr("fillCache", err).Msgf("Error serializing cache for ModelTemplate")
}
if err := client.Set(key, string(serialized), CacheTTL).Err(); err != nil {
log.Warn().AnErr("fillCache", err).Msgf("Error filling cache for ModelTemplate")
}
}
func fillNegCacheModelTemplate(client *redis.Client, key string) {
if err := client.Set("neg:"+key, "neg", NegCacheTTL).Err(); err != nil {
log.Warn().AnErr("fillNegCache", err).Msgf("Error filling neg cache for ModelTemplate")
}
}
// Misc functions.