Documentation

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