Ember data: convey species/strain relationships.
This commit is contained in:
		
							parent
							
								
									61c24fc843
								
							
						
					
					
						commit
						4be150f897
					
				
					 6 changed files with 32 additions and 23 deletions
				
			
		|  | @ -8,7 +8,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	DB.AddTableWithName(models.Species{}, "species").SetKeys(true, "Id") | 	DB.AddTableWithName(models.SpeciesBase{}, "species").SetKeys(true, "Id") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type speciesStore struct { | type speciesStore struct { | ||||||
|  | @ -17,7 +17,8 @@ type speciesStore struct { | ||||||
| 
 | 
 | ||||||
| func (s *speciesStore) Get(id int64) (*models.Species, error) { | func (s *speciesStore) Get(id int64) (*models.Species, error) { | ||||||
| 	var species models.Species | 	var species models.Species | ||||||
| 	if err := s.dbh.SelectOne(&species, `SELECT * FROM species WHERE id=$1;`, id); err != nil { | 	err := s.dbh.SelectOne(&species, `SELECT sp.*, array_agg(st.id) AS strains FROM species sp LEFT OUTER JOIN strains st ON st.species_id=sp.id WHERE sp.id=$1 GROUP BY sp.id;`, id) | ||||||
|  | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if &species == nil { | 	if &species == nil { | ||||||
|  | @ -30,9 +31,11 @@ func (s *speciesStore) Create(species *models.Species) (bool, error) { | ||||||
| 	currentTime := time.Now() | 	currentTime := time.Now() | ||||||
| 	species.CreatedAt = currentTime | 	species.CreatedAt = currentTime | ||||||
| 	species.UpdatedAt = currentTime | 	species.UpdatedAt = currentTime | ||||||
| 	if err := s.dbh.Insert(species); err != nil { | 	base := species.SpeciesBase | ||||||
|  | 	if err := s.dbh.Insert(base); err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  | 	species.Id = base.Id | ||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -41,13 +44,13 @@ func (s *speciesStore) List(opt *models.SpeciesListOptions) ([]*models.Species, | ||||||
| 		opt = &models.SpeciesListOptions{} | 		opt = &models.SpeciesListOptions{} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sql := `SELECT * FROM species` | 	sql := `SELECT sp.*, array_agg(st.id) AS strains FROM species sp LEFT OUTER JOIN strains st ON st.species_id=sp.id` | ||||||
| 
 | 
 | ||||||
| 	var conds []string | 	var conds []string | ||||||
| 	var vals []interface{} | 	var vals []interface{} | ||||||
| 
 | 
 | ||||||
| 	if opt.Genus != "" { | 	if opt.Genus != "" { | ||||||
| 		conds = append(conds, "genus_id = (SELECT id FROM genera WHERE lower(genus_name) = $1)") | 		conds = append(conds, "sp.genus_id = (SELECT id FROM genera WHERE lower(genus_name) = $1)") | ||||||
| 		vals = append(vals, opt.Genus) | 		vals = append(vals, opt.Genus) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -55,7 +58,7 @@ func (s *speciesStore) List(opt *models.SpeciesListOptions) ([]*models.Species, | ||||||
| 		sql += " WHERE (" + strings.Join(conds, ") AND (") + ")" | 		sql += " WHERE (" + strings.Join(conds, ") AND (") + ")" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sql += ";" | 	sql += " GROUP BY sp.id;" | ||||||
| 
 | 
 | ||||||
| 	var species []*models.Species | 	var species []*models.Species | ||||||
| 	err := s.dbh.Select(&species, sql, vals...) | 	err := s.dbh.Select(&species, sql, vals...) | ||||||
|  | @ -76,7 +79,8 @@ func (s *speciesStore) Update(id int64, species *models.Species) (bool, error) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	species.UpdatedAt = time.Now() | 	species.UpdatedAt = time.Now() | ||||||
| 	changed, err := s.dbh.Update(species) | 
 | ||||||
|  | 	changed, err := s.dbh.Update(species.SpeciesBase) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  | @ -94,7 +98,7 @@ func (s *speciesStore) Delete(id int64) (bool, error) { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	deleted, err := s.dbh.Delete(species) | 	deleted, err := s.dbh.Delete(species.SpeciesBase) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -11,17 +11,17 @@ import ( | ||||||
| func insertSpecies(t *testing.T, tx *modl.Transaction) *models.Species { | func insertSpecies(t *testing.T, tx *modl.Transaction) *models.Species { | ||||||
| 	// clean up our target table | 	// clean up our target table | ||||||
| 	tx.Exec(`DELETE FROM species;`) | 	tx.Exec(`DELETE FROM species;`) | ||||||
| 	species := newSpecies(t, tx) | 	s := newSpecies(t, tx) | ||||||
| 	if err := tx.Insert(species); err != nil { | 	if err := tx.Insert(s); err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	return species | 	return &models.Species{s, []int64(nil)} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func newSpecies(t *testing.T, tx *modl.Transaction) *models.Species { | func newSpecies(t *testing.T, tx *modl.Transaction) *models.SpeciesBase { | ||||||
| 	// we want to create and insert a genus record, too | 	// we want to create and insert a genus record, too | ||||||
| 	genus := insertGenus(t, tx) | 	genus := insertGenus(t, tx) | ||||||
| 	return &models.Species{GenusId: genus.Id, SpeciesName: "Test Species"} | 	return &models.SpeciesBase{GenusId: genus.Id, SpeciesName: "Test Species"} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestSpeciesStore_Get_db(t *testing.T) { | func TestSpeciesStore_Get_db(t *testing.T) { | ||||||
|  | @ -48,11 +48,11 @@ func TestSpeciesStore_Create_db(t *testing.T) { | ||||||
| 	tx, _ := DB.Begin() | 	tx, _ := DB.Begin() | ||||||
| 	defer tx.Rollback() | 	defer tx.Rollback() | ||||||
| 
 | 
 | ||||||
| 	species := newSpecies(t, tx) | 	base_species := newSpecies(t, tx) | ||||||
|  | 	species := models.Species{base_species, []int64(nil)} | ||||||
| 
 | 
 | ||||||
| 	d := NewDatastore(tx) | 	d := NewDatastore(tx) | ||||||
| 
 | 	created, err := d.Species.Create(&species) | ||||||
| 	created, err := d.Species.Create(species) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // A Species is a high-level classifier in bactdb. | // A Species is a high-level classifier in bactdb. | ||||||
| type Species struct { | type SpeciesBase struct { | ||||||
| 	Id          int64     `json:"id,omitempty"` | 	Id          int64     `json:"id,omitempty"` | ||||||
| 	GenusId     int64     `db:"genus_id" json:"genus"` | 	GenusId     int64     `db:"genus_id" json:"genus"` | ||||||
| 	SpeciesName string    `db:"species_name" json:"speciesName"` | 	SpeciesName string    `db:"species_name" json:"speciesName"` | ||||||
|  | @ -20,6 +20,11 @@ type Species struct { | ||||||
| 	DeletedAt   NullTime  `db:"deleted_at" json:"deletedAt"` | 	DeletedAt   NullTime  `db:"deleted_at" json:"deletedAt"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type Species struct { | ||||||
|  | 	*SpeciesBase | ||||||
|  | 	Strain NullSliceInt64 `db:"strains" json:"strains"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type SpeciesJSON struct { | type SpeciesJSON struct { | ||||||
| 	Species *Species `json:"species"` | 	Species *Species `json:"species"` | ||||||
| } | } | ||||||
|  | @ -33,7 +38,7 @@ func (m *Species) String() string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewSpecies() *Species { | func NewSpecies() *Species { | ||||||
| 	return &Species{SpeciesName: "Test Species"} | 	return &Species{&SpeciesBase{SpeciesName: "Test Species"}, make([]int64, 0)} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SpeciesService interacts with the species-related endpoints in bactdb's API. | // SpeciesService interacts with the species-related endpoints in bactdb's API. | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ func TestSpeciesService_Create(t *testing.T) { | ||||||
| 	mux.HandleFunc(urlPath(t, router.CreateSpecies, nil), func(w http.ResponseWriter, r *http.Request) { | 	mux.HandleFunc(urlPath(t, router.CreateSpecies, nil), func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		called = true | 		called = true | ||||||
| 		testMethod(t, r, "POST") | 		testMethod(t, r, "POST") | ||||||
| 		testBody(t, r, `{"species":{"id":1,"genus":1,"speciesName":"Test Species","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | 		testBody(t, r, `{"species":{"id":1,"genus":1,"speciesName":"Test Species","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null,"strains":[]}}`+"\n") | ||||||
| 
 | 
 | ||||||
| 		w.WriteHeader(http.StatusCreated) | 		w.WriteHeader(http.StatusCreated) | ||||||
| 		writeJSON(w, want) | 		writeJSON(w, want) | ||||||
|  | @ -124,7 +124,7 @@ func TestSpeciesService_Update(t *testing.T) { | ||||||
| 	mux.HandleFunc(urlPath(t, router.UpdateSpecies, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | 	mux.HandleFunc(urlPath(t, router.UpdateSpecies, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		called = true | 		called = true | ||||||
| 		testMethod(t, r, "PUT") | 		testMethod(t, r, "PUT") | ||||||
| 		testBody(t, r, `{"species":{"id":1,"genus":1,"speciesName":"Test Species Updated","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | 		testBody(t, r, `{"species":{"id":1,"genus":1,"speciesName":"Test Species Updated","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null,"strains":[]}}`+"\n") | ||||||
| 
 | 
 | ||||||
| 		w.WriteHeader(http.StatusOK) | 		w.WriteHeader(http.StatusOK) | ||||||
| 		writeJSON(w, want) | 		writeJSON(w, want) | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ import ( | ||||||
| // A Strain is a subclass of species | // A Strain is a subclass of species | ||||||
| type Strain struct { | type Strain struct { | ||||||
| 	Id             int64      `json:"id,omitempty"` | 	Id             int64      `json:"id,omitempty"` | ||||||
| 	SpeciesId      int64      `db:"species_id" json:"speciesId"` | 	SpeciesId      int64      `db:"species_id" json:"species"` | ||||||
| 	StrainName     string     `db:"strain_name" json:"strainName"` | 	StrainName     string     `db:"strain_name" json:"strainName"` | ||||||
| 	StrainType     string     `db:"strain_type" json:"strainType"` | 	StrainType     string     `db:"strain_type" json:"strainType"` | ||||||
| 	Etymology      NullString `db:"etymology" json:"etymology"` | 	Etymology      NullString `db:"etymology" json:"etymology"` | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ func TestStrainService_Create(t *testing.T) { | ||||||
| 	mux.HandleFunc(urlPath(t, router.CreateStrain, nil), func(w http.ResponseWriter, r *http.Request) { | 	mux.HandleFunc(urlPath(t, router.CreateStrain, nil), func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		called = true | 		called = true | ||||||
| 		testMethod(t, r, "POST") | 		testMethod(t, r, "POST") | ||||||
| 		testBody(t, r, `{"strain":{"id":1,"speciesId":1,"strainName":"Test Strain","strainType":"Test Type","etymology":"Test Etymology","accessionBanks":"Test Accession","genbankEmblDdb":"Test Genbank","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | 		testBody(t, r, `{"strain":{"id":1,"species":1,"strainName":"Test Strain","strainType":"Test Type","etymology":"Test Etymology","accessionBanks":"Test Accession","genbankEmblDdb":"Test Genbank","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | ||||||
| 
 | 
 | ||||||
| 		w.WriteHeader(http.StatusCreated) | 		w.WriteHeader(http.StatusCreated) | ||||||
| 		writeJSON(w, want) | 		writeJSON(w, want) | ||||||
|  | @ -124,7 +124,7 @@ func TestStrainService_Update(t *testing.T) { | ||||||
| 	mux.HandleFunc(urlPath(t, router.UpdateStrain, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | 	mux.HandleFunc(urlPath(t, router.UpdateStrain, map[string]string{"Id": "1"}), func(w http.ResponseWriter, r *http.Request) { | ||||||
| 		called = true | 		called = true | ||||||
| 		testMethod(t, r, "PUT") | 		testMethod(t, r, "PUT") | ||||||
| 		testBody(t, r, `{"strain":{"id":1,"speciesId":1,"strainName":"Test Strain Updated","strainType":"Test Type Updated","etymology":"Test Etymology","accessionBanks":"Test Accession Updated","genbankEmblDdb":"Test Genbank Updated","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | 		testBody(t, r, `{"strain":{"id":1,"species":1,"strainName":"Test Strain Updated","strainType":"Test Type Updated","etymology":"Test Etymology","accessionBanks":"Test Accession Updated","genbankEmblDdb":"Test Genbank Updated","isolatedFrom":null,"createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}}`+"\n") | ||||||
| 		w.WriteHeader(http.StatusOK) | 		w.WriteHeader(http.StatusOK) | ||||||
| 		writeJSON(w, want) | 		writeJSON(w, want) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Matthew Dillon
						Matthew Dillon