Lifecycle Hooks & Soft Deletes

Understanding how GORM interacts with the Rays IoC container is crucial for structuring your enterprise business logic securely.

1. Model Hooks vs. Service Logic (The IoC Boundary)

GORM provides native model hooks (e.g., BeforeCreate, AfterSave). It is tempting to place all your pre-save logic in these hooks, but you must respect the IoC Boundary.

Models (like User{}) are instantiated by the database driver, not by the Rays IoC container. Therefore, you cannot use the @:"Inject" tag inside a GORM model.

The Rule of Thumb

  • Static/Pure Logic (UUID generation, default timestamps) belongs in a Model Hook.
  • Dynamic/Impure Logic (Password Hashing via an injected PasswordEncoder, Audit Logging) belongs in the Service Layer.

Good Practice: Static Logic in the Model

func (u *User) BeforeCreate(tx *gorm.DB) error {
    // Perfectly fine: No injected dependencies required!
    if u.UUID == "" {
        u.UUID = uuid.New().String()
    }
    return nil
}

Good Practice: Dependency Logic in the Service

type UserService struct {
    Service
    repo    *UserRepository  `@:"Inject"`
    encoder *PasswordEncoder `@:"Inject"` // We need this!
}

func (s *UserService) Register(email, rawPassword string) Error {
    // The hashing requires an injected dependency, so it MUST happen
    // in the Service layer before calling the repository.
    hashed, _ := s.encoder.Hash(rawPassword)

    user := &User{Email: email, Password: hashed}
    _, err := s.repo.Save(user)

    return err
}

2. Soft Deletes

Enterprise applications rarely execute physical DELETE statements. Maintaining a historical audit trail requires “Soft Deletes”.

Because Rays ORM wraps GORM natively, soft deletes are fully supported without any extra configuration. If your model struct embeds gorm.Model or explicitly includes a gorm.DeletedAt field, the framework adapts automatically.

import "gorm.io/gorm"

type User struct {
    gorm.Model // Automatically includes ID, CreatedAt, UpdatedAt, and DeletedAt
    Email string
}

When you call the standard base repository methods:

  • repo.Delete(user)
  • repo.DeleteById(userId)

Rays ORM will automatically execute an UPDATE statement setting the deleted_at column, rather than a hard DELETE statement. Subsequent auto-generated queries (FindByName) will automatically filter out soft-deleted records.