Declaratively convert structs into data access objects.
Before I dive into explaining how to use this library, let me first show an example of how you will interface with your models after everything is set up:
// Inserts
myAnimal := models.NewAnimal()
myAnimal.Name = "Rigby"
myAnimal.Age = 3
myAnimal.Insert()
// Loads
myAnimalCopy := models.NewAnimal()
myAnimalCopy.Id = myAnimal.Id
myAnimalCopy.Load() // After this, myAnimalCopy's Name will be "Rigby" and Age will be 3 (pulled from the database)
// Updates
myAnimalCopy.Age = 4
myAnimalCopy.Update() // Updates Age in the database
// Deletes
myAnimalCopy.Delete() // Deletes from the database
Before we start anything, we'll need to create a model. For example, we will use an Animal.
type Animal struct {
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
After this is set up, we can now embed a surf.Model
into our model.
type Animal struct {
surf.Model
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
A surf.Model
is actually just an interface, so we'll need to decide what type of model we want to use!
There's more information below on
surf.Model
.
For this example, we are going to be using a surf.PqModel
.
You can make the decision of what surf.Model
to pack into your model at run time, but it will probably be easiest to create a constructor type function and create your models through that. Here I provide a constructor type fuction, but also a Prep
function which will provide you the flexibility to not use the constructor.
func NewAnimal() *Animal {
animal := new(Animal)
return animal.Prep()
}
func (a *Animal) Prep() *Animal {
a.Model = &surf.PqModel{
Database: db.Get(), // This is a *sql.DB, with github.com/lib/pq as a driver
Config: // TODO
}
return a
}
You'll notice in the last code snippet in the previous section, there is a // TODO
mark.
In that Prep
method, we still need to define the Config.
Before going into detail, here is the Prep
method with a fully filled out Config.
func (a *Animal) Prep() *Animal {
a.Model = &surf.PqModel{
Database: db.Get(),
Config: surf.Configuration{
TableName: "animals",
Fields: []surf.Field{
{
Pointer: &a.Id,
Name: "id",
UniqueIdentifier: true,
IsSet: func(pointer interface{}) bool {
pointerInt := *pointer.(*int)
return pointerInt != 0
},
},
{
Pointer: &a.Name,
Name: "name",
Insertable: true,
Updatable: true,
},
{
Pointer: &a.Age,
Name: "age",
Insertable: true,
Updatable: true,
},
},
},
}
return a
}
A surf.Configuration
has two fields, TableName
and Fields
.
TableName
is simply a string that represents your table/collection name in your datastore.
Fields
is an array of surf.Field
(which is explained in detail below).
A surf.Field
defines how a surf.Model
will interact with a field.
a surf.Field
contains a few values that determine this interaction:
This is a pointer to the surf.BaseModel
's field.
This is the name of the field as specified in the datastore.
This value specifies if this surf.Field
is to be considered by the Insert()
method of our model.
This value specifies if this surf.Field
is to be considered by the Update()
method of our model.
Note:
IsSet
is also required ifUniqueIdentifier
is set to true.
This value specifies that this field can unique identify an entry in the datastore.
You do not need to set this to true for all of your UNIQUE
fields in your datastore, but you can.
Setting UniqueIdentifier
to true gives you the following:
- The ability to set that fields value in the
surf.BaseModel
and callLoad()
against it. - Call
Update()
with this field in the where clause / filter - Call
Delete()
with this field in the where clause / filter.
This is a function that determines if the value in the struct is set or not.
This is only required if UniqueIdentifier
is set to true
.
This function will likely look something like this:
// ...
IsSet: func(pointer interface{}) bool {
pointerInt := *pointer.(*int)
return pointerInt != 0
},
// ...
This field has no meaning to Surf itself, so if you are only using Surf, don't worry about this field.
This field is used in Turf to skip the validation process in auto-generated controllers.
Models are simply implementations that adhere to the following interface:
type Model interface {
Insert() error
Load() error
Update() error
Delete() error
BulkFetch(BulkFetchConfig, BuildModel) ([]Model, error)
GetConfiguration() *Configuration
}
Right now in this library there is only
surf.PqModel
written, but I plan to at minimum write a MySQL model in the near future.
surf.PqModel
is written on top of github.com/lib/pq. This converts your struct into a DAO that can speak with PostgreSQL.
Before running tests, you must set up a database with a single table.
CREATE TABLE animals(
id serial PRIMARY KEY,
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
age int NOT NULL
);
You'll then need to have an environment variable set pointing to the database URL:
SERF_TEST_DATABASE_URL=""
After this is all set up you can run go test
the following to run the tests. To check the coverage run go test -cover
Thanks to @roideuniverse for some early guidance that ultimately lead into the creation of this library.