Models
Overview
Models represent domain entities with their data and business rules. They define the core structure of your application’s data and relationships.
Basic Model Structure
Standard Entity Pattern
package mdl
import "time"
type Garden struct {
// Standard fields
Id string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Domain-specific fields
Name string `json:"name"`
Description string `json:"description"`
Location string `json:"location"`
UserId string `json:"user_id"`
Active bool `json:"active"`
// Relationships (populated by Hydrators)
User *User `json:"user,omitempty"`
Plants []*Plant `json:"plants,omitempty"`
GardenTasks []*GardenTask `json:"garden_tasks,omitempty"`
}
// Required method for Entity interface
func (this *Garden) GetId() string {
return this.Id
}
Field Types
type Plant struct {
Id string `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// String fields
Name string `json:"name"`
Species string `json:"species"`
// Numeric fields
Height float64 `json:"height"`
Age int `json:"age"`
// Boolean fields
Flowering bool `json:"flowering"`
Edible bool `json:"edible"`
// Date fields
PlantedAt *time.Time `json:"planted_at,omitempty"`
HarvestedAt *time.Time `json:"harvested_at,omitempty"`
// Foreign keys
GardenId string `json:"garden_id"`
VarietyId *string `json:"variety_id,omitempty"` // Optional
}
Model Relationships
One-to-Many Relationships
type Garden struct {
Id string `json:"id"`
Name string `json:"name"`
UserId string `json:"user_id"` // Foreign key
// Relationship - loaded by Hydrator
Plants []*Plant `json:"plants,omitempty"`
}
type Plant struct {
Id string `json:"id"`
Name string `json:"name"`
GardenId string `json:"garden_id"` // Foreign key
// Relationship - loaded by Hydrator
Garden *Garden `json:"garden,omitempty"`
}
Many-to-One Relationships
type PlantTask struct {
Id string `json:"id"`
TaskName string `json:"task_name"`
PlantId string `json:"plant_id"` // Foreign key
AssigneeId string `json:"assignee_id"` // Foreign key
// Relationships
Plant *Plant `json:"plant,omitempty"`
Assignee *User `json:"assignee,omitempty"`
}
Optional Relationships
type Plant struct {
Id string `json:"id"`
Name string `json:"name"`
VarietyId *string `json:"variety_id,omitempty"` // Optional foreign key
// Optional relationship
Variety *PlantVariety `json:"variety,omitempty"`
}
Model Validation
Business Rules in Models
func (this *Garden) Validate() error {
if this.Name == "" {
return errors.New("garden name is required")
}
if len(this.Name) > 100 {
return errors.New("garden name cannot exceed 100 characters")
}
if this.UserId == "" {
return errors.New("garden must have an owner")
}
return nil
}
func (this *Plant) CanBeHarvested() bool {
if !this.Edible {
return false
}
if this.HarvestedAt != nil {
return false // Already harvested
}
// Check if mature enough (example: 90 days)
if this.PlantedAt != nil && time.Since(*this.PlantedAt) < 90*24*time.Hour {
return false
}
return true
}
Model Methods
Computed Properties
func (this *Garden) GetPlantCount() int {
return len(this.Plants)
}
func (this *Garden) GetActiveTaskCount() int {
count := 0
for _, task := range this.GardenTasks {
if !task.Completed {
count++
}
}
return count
}
func (this *Plant) GetAgeInDays() int {
if this.PlantedAt == nil {
return 0
}
return int(time.Since(*this.PlantedAt).Hours() / 24)
}
Status Methods
func (this *Garden) IsActive() bool {
return this.Active
}
func (this *Plant) IsMature() bool {
return this.GetAgeInDays() >= 60 // Example maturity
}
func (this *GardenTask) IsOverdue() bool {
return !this.Completed && this.DueDate.Before(time.Now())
}
JSON Serialization
Custom JSON Tags
type Garden struct {
Id string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Omit sensitive data from JSON
InternalNotes string `json:"-"`
// Conditional inclusion
User *User `json:"user,omitempty"`
Plants []*Plant `json:"plants,omitempty"`
}
Custom JSON Methods
func (this *Garden) MarshalJSON() ([]byte, error) {
type Alias Garden
return json.Marshal(&struct {
*Alias
PlantCount int `json:"plant_count"`
}{
Alias: (*Alias)(this),
PlantCount: this.GetPlantCount(),
})
}
Model Factory Patterns
Constructor Functions
func NewGarden(name, description, userId string) *Garden {
now := time.Now()
return &Garden{
Id: generateID(),
Name: name,
Description: description,
UserId: userId,
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
}
func NewPlant(name, species, gardenId string) *Plant {
now := time.Now()
return &Plant{
Id: generateID(),
Name: name,
Species: species,
GardenId: gardenId,
Flowering: false,
CreatedAt: now,
UpdatedAt: now,
}
}
Model Collections
Collection Methods
type Gardens []*Garden
func (gardens Gardens) FilterActive() Gardens {
var active Gardens
for _, garden := range gardens {
if garden.IsActive() {
active = append(active, garden)
}
}
return active
}
func (gardens Gardens) FindByUserId(userId string) Gardens {
var userGardens Gardens
for _, garden := range gardens {
if garden.UserId == userId {
userGardens = append(userGardens, garden)
}
}
return userGardens
}
func (gardens Gardens) TotalPlantCount() int {
total := 0
for _, garden := range gardens {
total += garden.GetPlantCount()
}
return total
}
Model Testing
Unit Testing Models
func TestGarden_Validate(t *testing.T) {
// Valid garden
garden := &Garden{
Name: "Test Garden",
UserId: "user123",
}
err := garden.Validate()
assert.NoError(t, err)
// Invalid garden - no name
garden.Name = ""
err = garden.Validate()
assert.Error(t, err)
assert.Contains(t, err.Error(), "name is required")
}
func TestPlant_CanBeHarvested(t *testing.T) {
plantedDate := time.Now().AddDate(0, 0, -100) // 100 days ago
plant := &Plant{
Edible: true,
PlantedAt: &plantedDate,
}
assert.True(t, plant.CanBeHarvested())
// Mark as harvested
harvestedDate := time.Now()
plant.HarvestedAt = &harvestedDate
assert.False(t, plant.CanBeHarvested())
}
LLM Model Development Notes
- Models represent domain entities with data and behavior
- Include standard Id, CreatedAt, UpdatedAt fields
- Use pointer types for optional fields (*time.Time, *string)
- Implement GetId() method for Entity interface
- Define relationships but let Hydrators populate them
- Add business logic methods for domain rules
- Use JSON tags appropriately (omitempty, -)
- Test model validation and business logic methods
- Keep models focused on data and domain rules