From 533adbdd09cd15090dfcb3bf6636d9fb23db8a6c Mon Sep 17 00:00:00 2001
From: Matthew Dillon <mrdillon@alaska.edu>
Date: Mon, 13 Oct 2014 09:55:20 -0800
Subject: [PATCH] Genera: datastore.

---
 datastore/datastore.go                        |  9 ++-
 datastore/genera.go                           | 47 +++++++++++
 datastore/genera_test.go                      | 81 +++++++++++++++++++
 datastore/migrations/00002_AddGenera_down.sql |  5 ++
 datastore/migrations/00002_AddGenera_up.sql   | 19 +++++
 5 files changed, 158 insertions(+), 3 deletions(-)
 create mode 100644 datastore/genera.go
 create mode 100644 datastore/genera_test.go
 create mode 100644 datastore/migrations/00002_AddGenera_down.sql
 create mode 100644 datastore/migrations/00002_AddGenera_up.sql

diff --git a/datastore/datastore.go b/datastore/datastore.go
index b18c012..94ff786 100644
--- a/datastore/datastore.go
+++ b/datastore/datastore.go
@@ -7,8 +7,9 @@ import (
 
 // A datastore access point (in PostgreSQL)
 type Datastore struct {
-	Users models.UsersService
-	dbh   modl.SqlExecutor
+	Users  models.UsersService
+	Genera models.GeneraService
+	dbh    modl.SqlExecutor
 }
 
 // NewDatastore creates a new client for accessing the datastore (in PostgreSQL).
@@ -20,11 +21,13 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore {
 
 	d := &Datastore{dbh: dbh}
 	d.Users = &usersStore{d}
+	d.Genera = &generaStore{d}
 	return d
 }
 
 func NewMockDatastore() *Datastore {
 	return &Datastore{
-		Users: &models.MockUsersService{},
+		Users:  &models.MockUsersService{},
+		Genera: &models.MockGeneraService{},
 	}
 }
diff --git a/datastore/genera.go b/datastore/genera.go
new file mode 100644
index 0000000..260ff07
--- /dev/null
+++ b/datastore/genera.go
@@ -0,0 +1,47 @@
+package datastore
+
+import (
+	"strings"
+
+	"github.com/thermokarst/bactdb/models"
+)
+
+func init() {
+	DB.AddTableWithName(models.Genus{}, "genera").SetKeys(true, "Id")
+}
+
+type generaStore struct {
+	*Datastore
+}
+
+func (s *generaStore) Get(id int64) (*models.Genus, error) {
+	var genus []*models.Genus
+	if err := s.dbh.Select(&genus, `SELECT * FROM genera WHERE id=$1;`, id); err != nil {
+		return nil, err
+	}
+	if len(genus) == 0 {
+		return nil, models.ErrGenusNotFound
+	}
+	return genus[0], nil
+}
+
+func (s *generaStore) Create(genus *models.Genus) (bool, error) {
+	if err := s.dbh.Insert(genus); err != nil {
+		if strings.Contains(err.Error(), `violates unique constraint "genus_idx"`) {
+			return false, err
+		}
+	}
+	return true, nil
+}
+
+func (s *generaStore) List(opt *models.GenusListOptions) ([]*models.Genus, error) {
+	if opt == nil {
+		opt = &models.GenusListOptions{}
+	}
+	var genera []*models.Genus
+	err := s.dbh.Select(&genera, `SELECT * FROM genera LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset())
+	if err != nil {
+		return nil, err
+	}
+	return genera, nil
+}
diff --git a/datastore/genera_test.go b/datastore/genera_test.go
new file mode 100644
index 0000000..5eebae2
--- /dev/null
+++ b/datastore/genera_test.go
@@ -0,0 +1,81 @@
+package datastore
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/thermokarst/bactdb/models"
+)
+
+func TestGeneraStore_Get_db(t *testing.T) {
+	want := &models.Genus{Id: 1, GenusName: "Test Genus"}
+
+	tx, _ := DB.Begin()
+	defer tx.Rollback()
+
+	// Test on a clean database
+	tx.Exec(`DELETE FROM genera;`)
+	if err := tx.Insert(want); err != nil {
+		t.Fatal(err)
+	}
+
+	d := NewDatastore(tx)
+	genus, err := d.Genera.Get(1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt)
+	if !reflect.DeepEqual(genus, want) {
+		t.Errorf("got genus %+v, want %+v", genus, want)
+	}
+}
+
+func TestGeneraStore_Create_db(t *testing.T) {
+	genus := &models.Genus{Id: 1, GenusName: "Test Genus"}
+
+	tx, _ := DB.Begin()
+	defer tx.Rollback()
+
+	// Test on a clean database
+	tx.Exec(`DELETE FROM genera;`)
+
+	d := NewDatastore(tx)
+	created, err := d.Genera.Create(genus)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !created {
+		t.Error("!created")
+	}
+	if genus.Id == 0 {
+		t.Error("want nonzero genus.Id after submitting")
+	}
+}
+
+func TestGeneraStore_List_db(t *testing.T) {
+	want := []*models.Genus{{Id: 1, GenusName: "Test Genus"}}
+
+	tx, _ := DB.Begin()
+	defer tx.Rollback()
+
+	// Test on a clean database
+	tx.Exec(`DELETE FROM genera;`)
+	if err := tx.Insert(want[0]); err != nil {
+		t.Fatal(err)
+	}
+
+	d := NewDatastore(tx)
+	genera, err := d.Genera.List(&models.GenusListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, g := range want {
+		normalizeTime(&g.CreatedAt, &g.UpdatedAt, &g.DeletedAt)
+	}
+	if !reflect.DeepEqual(genera, want) {
+		t.Errorf("got genera %+v, want %+v", genera, want)
+	}
+}
diff --git a/datastore/migrations/00002_AddGenera_down.sql b/datastore/migrations/00002_AddGenera_down.sql
new file mode 100644
index 0000000..672e1fc
--- /dev/null
+++ b/datastore/migrations/00002_AddGenera_down.sql
@@ -0,0 +1,5 @@
+-- bactdb
+-- Matthew R Dillon
+
+DROP TABLE genera;
+
diff --git a/datastore/migrations/00002_AddGenera_up.sql b/datastore/migrations/00002_AddGenera_up.sql
new file mode 100644
index 0000000..a9e0f5c
--- /dev/null
+++ b/datastore/migrations/00002_AddGenera_up.sql
@@ -0,0 +1,19 @@
+-- bactdb
+-- Matthew R Dillon
+
+CREATE TABLE genera (
+    id BIGSERIAL NOT NULL,
+    genusname CHARACTER VARYING(100),
+
+    created_at TIMESTAMP WITH TIME ZONE,
+    updated_at TIMESTAMP WITH TIME ZONE,
+    deleted_at TIMESTAMP WITH TIME ZONE,
+
+    CONSTRAINT genus_pkey PRIMARY KEY (id)
+);
+
+CREATE UNIQUE INDEX genusname_idx
+    ON genera
+    USING btree
+    (genusname COLLATE pg_catalog."default");
+