Surf is a high level datastore worker that provides CRUD operations for your models.
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 everthing 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.Worker
into our model.
type Animal struct {
surf.Worker
Id int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
A surf.Worker
is actually just an interface, so we'll need to decide what type of worker we want to use!
There's more information below on
surf.Worker
.
For this example, we are going to be using a surf.PqWorker
.
You can make the decision of what surf.Worker
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.Worker = &surf.PqWorker{
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.Worker = &surf.PqWorker{
Database: db.Get(),
Config: surf.Configuration{
TableName: "animals",
Fields: []surf.Field{
surf.Field{
Pointer: &a.Id,
Name: "id",
UniqueIdentifier: true,
IsSet: func(pointer interface{}) bool {
pointerInt := *pointer.(*int)
return pointerInt != 0
},
},
surf.Field{
Pointer: &a.Name,
Name: "name",
Insertable: true,
Updatable: true,
},
surf.Field{
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.Worker
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 worker.
This value specifies if this surf.Field
is to be considered by the Update()
method of our worker.
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.
If you are using a
surf.Worker
that is backed by a relational database, it is strongly recommended that column is indexed.
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
},
// ...
Workers are simply implementations that adhere to the following interface:
type Worker interface {
Insert() error
Load() error
Update() error
Delete() error
}
Right now in this library there is only
surf.PqWorker
written, but I plan to at minimum write a MySQL worker in the near future.
surf.PqWorker
is written on top of github.com/lib/pq. This provides high level PostgreSQL CRUD operations to your models.
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.