Add automatic timestamps for created_at/updated_at
* Update and Insert * Add --no-auto-timestamps flag
This commit is contained in:
parent
677d45cef4
commit
96d40fcfe4
14 changed files with 201 additions and 60 deletions
14
README.md
14
README.md
|
@ -22,6 +22,7 @@ lifecycle.
|
|||
- Easy workflow (models can always be regenerated, full auto-complete)
|
||||
- Strongly typed querying (usually no converting or binding to pointers)
|
||||
- Hooks (Before/After Create/Update)
|
||||
- Automatic CreatedAt/UpdatedAt
|
||||
- Relationships/Associations
|
||||
- Eager loading
|
||||
- Transactions
|
||||
|
@ -29,17 +30,20 @@ lifecycle.
|
|||
- Compatibility tests (Run against your own DB schema)
|
||||
- Debug logging
|
||||
|
||||
#### Missing Features
|
||||
|
||||
- Automatic CreatedAt UpdatedAt (use Hooks instead)
|
||||
- Nested eager loading
|
||||
|
||||
#### Supported Databases
|
||||
|
||||
- PostgreSQL
|
||||
|
||||
Note: Seeking contributors for other database engines.
|
||||
|
||||
#### Automatic CreatedAt/UpdatedAt
|
||||
|
||||
If your generated SQLBoiler models package can find columns with the
|
||||
names `created_at` or `updated_at` it will automatically set them
|
||||
to `time.Now()` in your database, and update your object appropriately.
|
||||
|
||||
Note: You can set the timezone for this feature by calling `boil.SetLocation()`
|
||||
|
||||
#### Example Queries
|
||||
|
||||
```go
|
||||
|
|
29
boil/db.go
29
boil/db.go
|
@ -1,14 +1,6 @@
|
|||
package boil
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// currentDB is a global database handle for the package
|
||||
currentDB Executor
|
||||
)
|
||||
import "database/sql"
|
||||
|
||||
// Executor can perform SQL queries.
|
||||
type Executor interface {
|
||||
|
@ -30,15 +22,6 @@ type Beginner interface {
|
|||
Begin() (*sql.Tx, error)
|
||||
}
|
||||
|
||||
// DebugMode is a flag controlling whether generated sql statements and
|
||||
// debug information is outputted to the DebugWriter handle
|
||||
//
|
||||
// NOTE: This should be disabled in production to avoid leaking sensitive data
|
||||
var DebugMode = false
|
||||
|
||||
// DebugWriter is where the debug output will be sent if DebugMode is true
|
||||
var DebugWriter = os.Stdout
|
||||
|
||||
// Begin a transaction
|
||||
func Begin() (Transactor, error) {
|
||||
creator, ok := currentDB.(Beginner)
|
||||
|
@ -48,13 +31,3 @@ func Begin() (Transactor, error) {
|
|||
|
||||
return creator.Begin()
|
||||
}
|
||||
|
||||
// SetDB initializes the database handle for all template db interactions
|
||||
func SetDB(db Executor) {
|
||||
currentDB = db
|
||||
}
|
||||
|
||||
// GetDB retrieves the global state database handle
|
||||
func GetDB() Executor {
|
||||
return currentDB
|
||||
}
|
||||
|
|
50
boil/global.go
Normal file
50
boil/global.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package boil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// currentDB is a global database handle for the package
|
||||
currentDB Executor
|
||||
// timestampLocation is the timezone used for the
|
||||
// automated setting of created_at/updated_at columns
|
||||
timestampLocation *time.Location
|
||||
)
|
||||
|
||||
// DebugMode is a flag controlling whether generated sql statements and
|
||||
// debug information is outputted to the DebugWriter handle
|
||||
//
|
||||
// NOTE: This should be disabled in production to avoid leaking sensitive data
|
||||
var DebugMode = false
|
||||
|
||||
// DebugWriter is where the debug output will be sent if DebugMode is true
|
||||
var DebugWriter = os.Stdout
|
||||
|
||||
// SetDB initializes the database handle for all template db interactions
|
||||
func SetDB(db Executor) {
|
||||
currentDB = db
|
||||
}
|
||||
|
||||
// GetDB retrieves the global state database handle
|
||||
func GetDB() Executor {
|
||||
return currentDB
|
||||
}
|
||||
|
||||
// SetLocation sets the global timestamp Location.
|
||||
// This is the timezone used by the generated package for the
|
||||
// automated setting of created_at and updated_at columns.
|
||||
// If the package was generated with the --no-auto-timestamps flag
|
||||
// then this function has no effect.
|
||||
func SetLocation(loc *time.Location) {
|
||||
timestampLocation = loc
|
||||
}
|
||||
|
||||
// GetLocation retrieves the global timestamp Location.
|
||||
// This is the timezone used by the generated package for the
|
||||
// automated setting of created_at and updated_at columns
|
||||
// if the package was not generated with the --no-auto-timestamps flag.
|
||||
func GetLocation() *time.Location {
|
||||
return timestampLocation
|
||||
}
|
13
config.go
13
config.go
|
@ -2,12 +2,13 @@ package main
|
|||
|
||||
// Config for the running of the commands
|
||||
type Config struct {
|
||||
DriverName string `toml:"driver_name"`
|
||||
PkgName string `toml:"pkg_name"`
|
||||
OutFolder string `toml:"out_folder"`
|
||||
BaseDir string `toml:"base_dir"`
|
||||
ExcludeTables []string `toml:"exclude"`
|
||||
NoHooks bool `toml:"no_hooks"`
|
||||
DriverName string `toml:"driver_name"`
|
||||
PkgName string `toml:"pkg_name"`
|
||||
OutFolder string `toml:"out_folder"`
|
||||
BaseDir string `toml:"base_dir"`
|
||||
ExcludeTables []string `toml:"exclude"`
|
||||
NoHooks bool `toml:"no_hooks"`
|
||||
NoAutoTimestamps bool `toml:"no_auto_timestamps"`
|
||||
|
||||
Postgres PostgresConfig `toml:"postgres"`
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ var defaultTemplateImports = imports{
|
|||
`"fmt"`,
|
||||
`"strings"`,
|
||||
`"database/sql"`,
|
||||
`"time"`,
|
||||
},
|
||||
thirdParty: importList{
|
||||
`"github.com/pkg/errors"`,
|
||||
|
|
10
main.go
10
main.go
|
@ -66,6 +66,7 @@ func main() {
|
|||
rootCmd.PersistentFlags().StringSliceP("exclude", "x", nil, "Tables to be excluded from the generated package")
|
||||
rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug mode prints stack traces on error")
|
||||
rootCmd.PersistentFlags().BoolP("no-hooks", "", false, "Disable hooks feature for your models")
|
||||
rootCmd.PersistentFlags().BoolP("no-auto-timestamps", "", false, "Disable automatic timestamps for created_at/updated_at")
|
||||
|
||||
viper.SetDefault("postgres.sslmode", "require")
|
||||
viper.SetDefault("postgres.port", "5432")
|
||||
|
@ -102,10 +103,11 @@ func preRun(cmd *cobra.Command, args []string) error {
|
|||
driverName := args[0]
|
||||
|
||||
cmdConfig = &Config{
|
||||
DriverName: driverName,
|
||||
OutFolder: viper.GetString("output"),
|
||||
PkgName: viper.GetString("pkgname"),
|
||||
NoHooks: viper.GetBool("no-hooks"),
|
||||
DriverName: driverName,
|
||||
OutFolder: viper.GetString("output"),
|
||||
PkgName: viper.GetString("pkgname"),
|
||||
NoHooks: viper.GetBool("no-hooks"),
|
||||
NoAutoTimestamps: viper.GetBool("no-auto-timestamps"),
|
||||
}
|
||||
|
||||
// BUG: https://github.com/spf13/viper/issues/200
|
||||
|
|
24
sqlboiler.go
24
sqlboiler.go
|
@ -77,11 +77,12 @@ func New(config *Config) (*State, error) {
|
|||
// state given.
|
||||
func (s *State) Run(includeTests bool) error {
|
||||
singletonData := &templateData{
|
||||
Tables: s.Tables,
|
||||
DriverName: s.Config.DriverName,
|
||||
UseLastInsertID: s.Driver.UseLastInsertID(),
|
||||
PkgName: s.Config.PkgName,
|
||||
NoHooks: s.Config.NoHooks,
|
||||
Tables: s.Tables,
|
||||
DriverName: s.Config.DriverName,
|
||||
UseLastInsertID: s.Driver.UseLastInsertID(),
|
||||
PkgName: s.Config.PkgName,
|
||||
NoHooks: s.Config.NoHooks,
|
||||
NoAutoTimestamps: s.Config.NoAutoTimestamps,
|
||||
|
||||
StringFuncs: templateStringMappers,
|
||||
}
|
||||
|
@ -106,12 +107,13 @@ func (s *State) Run(includeTests bool) error {
|
|||
}
|
||||
|
||||
data := &templateData{
|
||||
Tables: s.Tables,
|
||||
Table: table,
|
||||
DriverName: s.Config.DriverName,
|
||||
UseLastInsertID: s.Driver.UseLastInsertID(),
|
||||
PkgName: s.Config.PkgName,
|
||||
NoHooks: s.Config.NoHooks,
|
||||
Tables: s.Tables,
|
||||
Table: table,
|
||||
DriverName: s.Config.DriverName,
|
||||
UseLastInsertID: s.Driver.UseLastInsertID(),
|
||||
PkgName: s.Config.PkgName,
|
||||
NoHooks: s.Config.NoHooks,
|
||||
NoAutoTimestamps: s.Config.NoAutoTimestamps,
|
||||
|
||||
StringFuncs: templateStringMappers,
|
||||
}
|
||||
|
|
|
@ -411,3 +411,17 @@ func StringSliceMatch(a []string, b []string) bool {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainsAny returns true if any of the passed in strings are
|
||||
// found in the passed in string slice
|
||||
func ContainsAny(a []string, finds ...string) bool {
|
||||
for _, s := range a {
|
||||
for _, find := range finds {
|
||||
if s == find {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -378,3 +378,32 @@ func TestStringSliceMatch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsAny(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
a := []string{"hello", "friend"}
|
||||
if ContainsAny([]string{}, "x") {
|
||||
t.Errorf("Should not contain x")
|
||||
}
|
||||
|
||||
if ContainsAny(a, "x") {
|
||||
t.Errorf("Should not contain x")
|
||||
}
|
||||
|
||||
if !ContainsAny(a, "hello") {
|
||||
t.Errorf("Should contain hello")
|
||||
}
|
||||
|
||||
if !ContainsAny(a, "friend") {
|
||||
t.Errorf("Should contain friend")
|
||||
}
|
||||
|
||||
if !ContainsAny(a, "hello", "friend") {
|
||||
t.Errorf("Should contain hello and friend")
|
||||
}
|
||||
|
||||
if ContainsAny(a) {
|
||||
t.Errorf("Should not return true")
|
||||
}
|
||||
}
|
||||
|
|
14
templates.go
14
templates.go
|
@ -13,12 +13,13 @@ import (
|
|||
|
||||
// templateData for sqlboiler templates
|
||||
type templateData struct {
|
||||
Tables []bdb.Table
|
||||
Table bdb.Table
|
||||
DriverName string
|
||||
UseLastInsertID bool
|
||||
PkgName string
|
||||
NoHooks bool
|
||||
Tables []bdb.Table
|
||||
Table bdb.Table
|
||||
DriverName string
|
||||
UseLastInsertID bool
|
||||
PkgName string
|
||||
NoHooks bool
|
||||
NoAutoTimestamps bool
|
||||
|
||||
StringFuncs map[string]func(string) string
|
||||
}
|
||||
|
@ -127,6 +128,7 @@ var templateFunctions = template.FuncMap{
|
|||
"joinSlices": strmangle.JoinSlices,
|
||||
"stringMap": strmangle.StringMap,
|
||||
"prefixStringSlice": strmangle.PrefixStringSlice,
|
||||
"containsAny": strmangle.ContainsAny,
|
||||
|
||||
// String Map ops
|
||||
"makeStringMap": strmangle.MakeStringMap,
|
||||
|
|
|
@ -22,3 +22,6 @@ type (
|
|||
*boil.Query
|
||||
}
|
||||
)
|
||||
|
||||
// Force time package dependency for automated UpdatedAt/CreatedAt.
|
||||
var _ = time.Second
|
||||
|
|
|
@ -32,6 +32,8 @@ func (o *{{$tableNameSingular}}) Insert(exec boil.Executor, whitelist ... string
|
|||
}
|
||||
|
||||
var err error
|
||||
{{- template "timestamp_insert_helper" . }}
|
||||
|
||||
{{if eq .NoHooks false -}}
|
||||
if err := o.doBeforeInsertHooks(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -35,6 +35,8 @@ func (o *{{$tableNameSingular}}) UpdateP(exec boil.Executor, whitelist ... strin
|
|||
// Update does not automatically update the record in case of default values. Use .Reload()
|
||||
// to refresh the records.
|
||||
func (o *{{$tableNameSingular}}) Update(exec boil.Executor, whitelist ... string) error {
|
||||
{{- template "timestamp_update_helper" . -}}
|
||||
|
||||
{{if eq .NoHooks false -}}
|
||||
if err := o.doBeforeUpdateHooks(); err != nil {
|
||||
return err
|
||||
|
|
56
templates/17_auto_timestamps.tpl
Normal file
56
templates/17_auto_timestamps.tpl
Normal file
|
@ -0,0 +1,56 @@
|
|||
{{- define "timestamp_insert_helper" -}}
|
||||
{{- if eq .NoAutoTimestamps false -}}
|
||||
{{- $colNames := .Table.Columns | columnNames -}}
|
||||
{{if containsAny $colNames "created_at" "updated_at"}}
|
||||
loc := boil.GetLocation()
|
||||
currTime := time.Time{}
|
||||
if loc != nil {
|
||||
currTime = time.Now().In(boil.GetLocation())
|
||||
} else {
|
||||
currTime = time.Now()
|
||||
}
|
||||
{{range $ind, $col := .Table.Columns}}
|
||||
{{- if eq $col.Name "created_at" -}}
|
||||
{{- if $col.Nullable}}
|
||||
o.CreatedAt.Time = currTime
|
||||
o.CreatedAt.Valid = true
|
||||
{{- else}}
|
||||
o.CreatedAt = currTime
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if eq $col.Name "updated_at" -}}
|
||||
{{- if $col.Nullable}}
|
||||
o.UpdatedAt.Time = currTime
|
||||
o.UpdatedAt.Valid = true
|
||||
{{- else}}
|
||||
o.UpdatedAt = currTime
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{- end}}
|
||||
{{- end -}}
|
||||
{{- define "timestamp_update_helper" -}}
|
||||
{{- if eq .NoAutoTimestamps false -}}
|
||||
{{- $colNames := .Table.Columns | columnNames -}}
|
||||
{{if containsAny $colNames "updated_at"}}
|
||||
loc := boil.GetLocation()
|
||||
currTime := time.Time{}
|
||||
if loc != nil {
|
||||
currTime = time.Now().In(boil.GetLocation())
|
||||
} else {
|
||||
currTime = time.Now()
|
||||
}
|
||||
{{range $ind, $col := .Table.Columns}}
|
||||
{{- if eq $col.Name "updated_at" -}}
|
||||
{{- if $col.Nullable}}
|
||||
o.UpdatedAt.Time = currTime
|
||||
o.UpdatedAt.Valid = true
|
||||
{{- else}}
|
||||
o.UpdatedAt = currTime
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{- end}}
|
||||
{{end -}}
|
Loading…
Reference in a new issue