github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/gen/store-crud.go (about)

     1  package gen
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"strings"
     7  
     8  	"github.com/spf13/pflag"
     9  )
    10  
    11  func init() {
    12  	globalMapGenerator["store-crud"] = GeneratorFunc(func(s *Settings, name string, args ...string) error {
    13  
    14  		fset := pflag.NewFlagSet("gen", pflag.ContinueOnError)
    15  		storeName := fset.String("store", "Store", "The name of the store struct to add methods to.")
    16  		modelName := fset.String("model", "", "The model object name, if not specified default will be deduced from file name.")
    17  		genericMode := fset.Bool("generic", false, "Generic mode outputs methods that use interface{} instead of the specific type and can have the underlying model object swapped out.")
    18  		tests := fset.Bool("tests", true, "Create test file with test(s) for these store methods.")
    19  		targetFile, data, err := ParsePFlagsAndOneFile(s, fset, args)
    20  		if err != nil {
    21  			return err
    22  		}
    23  
    24  		data["StoreName"] = *storeName
    25  		data["GenericMode"] = *genericMode
    26  
    27  		modelNameFixed := *modelName
    28  		if modelNameFixed == "" {
    29  			_, fname := path.Split(targetFile)
    30  			modelNameFixed = NameSnakeToCamel(fname, []string{"store-"}, nil)
    31  		}
    32  		data["ModelName"] = modelNameFixed
    33  
    34  		err = OutputGoSrcTemplate(s, data, targetFile, `
    35  package {{.PackageName}}
    36  
    37  import (
    38   	"context"
    39  
    40   	"github.com/bradleypeabody/gouuidv6"
    41   	"github.com/gocaveman/caveman/valid"
    42   	"github.com/gocaveman/tmeta/tmetadbr"
    43   	"github.com/gocaveman/tmeta/tmetautil"
    44   	"github.com/gocraft/dbr"
    45  )
    46  
    47  // Create{{.ModelName}} inserts the record into the database.
    48  func (s *{{.StoreName}}) Create{{.ModelName}}(ctx context.Context, o {{if .GenericMode}}interface{}{{else}}*{{.ModelName}}{{end}}) error {
    49  
    50  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	defer tx.RollbackUnlessCommitted()
    55  
    56  	err = valid.Obj(o, nil)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	b := tmetadbr.New(tx, s.Meta)
    62  	_, err = b.MustInsert(o).Exec()
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	return tx.Commit()
    68  }
    69  
    70  // Update{{.ModelName}} updates this record in the database.
    71  func (s *{{.StoreName}}) Update{{.ModelName}}(ctx context.Context, o {{if .GenericMode}}interface{}{{else}}*{{.ModelName}}{{end}}) error {
    72  
    73  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	defer tx.RollbackUnlessCommitted()
    78  
    79  	err = valid.Obj(o, nil)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	b := tmetadbr.New(tx, s.Meta)
    85  	err = b.ResultWithOneUpdate(b.MustUpdateByID(o).Exec())
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	return tx.Commit()
    91  }
    92  
    93  // Delete{{.ModelName}} deletes this record in the database.
    94  func (s *{{.StoreName}}) Delete{{.ModelName}}(ctx context.Context, o {{if .GenericMode}}interface{}{{else}}*{{.ModelName}}{{end}}) error {
    95  
    96  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	defer tx.RollbackUnlessCommitted()
   101  
   102  	b := tmetadbr.New(tx, s.Meta)
   103  	err = b.ResultWithOneUpdate(b.MustDeleteByID(o).Exec())
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	return tx.Commit()
   109  }
   110  
   111  // Fetch{{.ModelName}} get a record in the database by ID.
   112  func (s *{{.StoreName}}) Fetch{{.ModelName}}(ctx context.Context, o {{if .GenericMode}}interface{}{{else}}*{{.ModelName}}{{end}}, id string, related ...string) error {
   113  
   114  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	defer tx.RollbackUnlessCommitted()
   119  
   120  	b := tmetadbr.New(tx, s.Meta)
   121  	err = b.MustSelectByID(o, id).LoadOne(o)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	ti := s.Meta.For(o)
   127  	for _, r := range related {
   128  		rstmt, err := b.SelectRelation(o, r)
   129  		if err != nil {
   130  			return err
   131  		}
   132  		_, err = rstmt.Load(
   133  			ti.RelationTargetPtr(o, r))
   134  		if err != nil {
   135  			return err
   136  		}
   137  	}
   138  
   139  	return tx.Commit()
   140  }
   141  
   142  {{/* FIXME: security goes in controller - so move the strict check of the
   143  fields on criteria and orderby and related etc up up there
   144  */}}
   145  
   146  func (s *{{.StoreName}}) Search{{.ModelName}}Count(ctx context.Context, criteria tmetautil.Criteria, orderBy tmetautil.OrderByList, maxRows int64) (int64, error) {
   147  
   148  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
   149  	if err != nil {
   150  		return 0, err
   151  	}
   152  	defer tx.RollbackUnlessCommitted()
   153  
   154  	ti := s.Meta.For({{.ModelName}}{})
   155  
   156  	stmt := tx.Select(ti.SQLPKFields()...).From(ti.SQLName())
   157  
   158  	whereSql, args, err := criteria.SQL()
   159  	if err != nil {
   160  		return 0, err
   161  	}
   162  	if len(whereSql) > 0 {
   163  		stmt = stmt.Where(whereSql, args...)
   164  	}
   165  
   166  	for _, o := range orderBy {
   167  		stmt = stmt.OrderDir(o.Field, !o.Desc)
   168  	}
   169  
   170  	if maxRows >= 0 {
   171  		stmt = stmt.Limit(uint64(maxRows))
   172  	}
   173  
   174  	buf := dbr.NewBuffer()
   175  	err = stmt.Build(s.dbrc.Dialect, buf)
   176  	if err != nil {
   177  		return 0, err
   178  	}
   179  	innerSQL := buf.String()
   180  	args = buf.Value()
   181  
   182  	var c int64
   183  	err = tx.SelectBySql("SELECT count(1) c FROM ("+innerSQL+") t", args...).LoadOne(&c)
   184  	if err != nil {
   185  		return 0, err
   186  	}
   187  
   188  	return c, tx.Commit()
   189  }
   190  
   191  // Search{{.ModelName}} builds and runs a select statement from the input you provide.
   192  func (s *{{.StoreName}}) Search{{.ModelName}}(ctx context.Context, criteria tmetautil.Criteria, orderBy tmetautil.OrderByList, limit, offset int64, related ...string) ([]{{.ModelName}}, error) {
   193  
   194  	tx, err := s.dbrc.NewSession(s.EventReceiver).BeginTx(ctx, nil)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	defer tx.RollbackUnlessCommitted()
   199  
   200  	b := tmetadbr.New(tx, s.Meta)
   201  
   202  	ti := s.Meta.For({{.ModelName}}{})
   203  
   204  	stmt := tx.Select(ti.SQLFields(true)...).From(ti.SQLName())
   205  
   206  	whereSql, args, err := criteria.SQL()
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	if len(whereSql) > 0 {
   211  		stmt = stmt.Where(whereSql, args...)
   212  	}
   213  
   214  	for _, o := range orderBy {
   215  		stmt = stmt.OrderDir(o.Field, !o.Desc)
   216  	}
   217  
   218  	if offset > 0 {
   219  		stmt = stmt.Offset(uint64(offset))
   220  	}
   221  	if limit >= 0 {
   222  		stmt = stmt.Limit(uint64(limit))
   223  	}
   224  
   225  	// empty set should return zero length slice instead of nil for proper JSON output and semantic correctness
   226  	ret := make([]{{.ModelName}}, 0)
   227  	_, err = stmt.Load(&ret)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	if len(related) > 0 {
   233  		for i := range ret {
   234  			for _, r := range related {
   235  				rstmt, err := b.SelectRelation(&ret[i], r)
   236  				if err != nil {
   237  					return nil, err
   238  				}
   239  				_, err = rstmt.Load(
   240  					ti.RelationTargetPtr(&ret[i], r))
   241  				if err != nil {
   242  					return nil, err
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	return ret, tx.Commit()
   249  }
   250  
   251  
   252  {{/* NOTE: the paging/limits here are all based on the idea of not overloading the database server and putting sensible limits on how
   253  	much data a client can ask for. However it is completely feasible for clients to page through an arbitrarily large data set
   254  	by asking for the next N records where KEY > LAST_KEY, and clients don't need any special tools for that particularly
   255  	- this should definitely be documented somewhere */}}
   256  
   257  
   258  // TODO: figure out Find... methods... (think about listing page); also make sure empty
   259  // list returns zero length slice and not nil, so JSON marshaling etc work as expected
   260  // (it's also, while less efficient, semantically correct - the search didn't return "nothing"
   261  // it returned zero of the specified element - and this difference also shows up in
   262  // the resulting JSON)
   263  
   264  // TODO: upsert? - maybe it's an option to add an example if desired.
   265  
   266  // TODO: related/joins; also specifically look at having methods that say "set the
   267  // list of this type of join to exactly X set as one call in one transaction", rather
   268  // than having to delete and re-write.
   269  
   270  `, false)
   271  		if err != nil {
   272  			return err
   273  		}
   274  
   275  		// TODO: disable tests for now until we fill this in
   276  		if false && *tests {
   277  
   278  			testsTargetFile := strings.Replace(targetFile, ".go", "_test.go", 1)
   279  			if testsTargetFile == targetFile {
   280  				return fmt.Errorf("unable to determine test file name for %q", targetFile)
   281  			}
   282  
   283  			err = OutputGoSrcTemplate(s, data, testsTargetFile, `
   284  package {{.PackageName}}
   285  
   286  func Test{{.ModelName}}CRUD(t *testing.T) {
   287  
   288  	assert := assert.New(t)
   289  
   290  	dbDriver := "sqlite3"
   291  	dbDsn := "file:Test{{.ModelName}}CRUD?mode=memory&cache=shared"
   292  
   293  	ml := DefaultStoreMigrations.WithDriverName(dbDriver).Sorted()
   294  	versioner, err := migratedbr.New(dbDriver, dbDsn)
   295  	assert.NoError(err)
   296  	runner := migrate.NewRunner(dbDriver, dbDsn, versioner, ml)
   297  	err = runner.RunAllUpToLatest()
   298  	assert.NoError(err)
   299  
   300  	conn, err := dbr.Open(dbDriver, dbDsn, nil)
   301  	assert.NoError(err)
   302  	store := NewStore(dbrobj.NewConfig().NewConnector(conn, nil))
   303  
   304  	err = store.AfterWire()
   305  	assert.NoError(err)
   306  
   307  	o := &{{.ModelName}}{
   308  		// TODO: populate with valid data
   309  		// Category:    "testcat",
   310  		// Title:       "Clean the Kitchen",
   311  		// Description: "It's gross",
   312  	}
   313  	err = store.Create{{.ModelName}}(o)
   314  	assert.NoError(err)
   315  	// TODO: o.Title = "Deep Clean the Kitchen"
   316  	err = store.Update{{.ModelName}}(o)
   317  	assert.NoError(err)
   318  	o2 := &{{.ModelName}}{}
   319  	err = store.Fetch{{.ModelName}}(o2, o.{{.ModelName}}ID)
   320  	assert.NoError(err)
   321  	// TODO: assert.Equal("Deep Clean the Kitchen", o2.Title)
   322  	err = store.Delete{{.ModelName}}(o2)
   323  	assert.NoError(err)
   324  	err = store.Fetch{{.ModelName}}(o2)
   325  	assert.Error(err)
   326  
   327  }
   328  
   329  `, false)
   330  			if err != nil {
   331  				return err
   332  			}
   333  
   334  		}
   335  
   336  		return nil
   337  	})
   338  }