Skip to content

nullcache/gorm-plus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GORM Plus

A type-safe generic base model pattern implementation for GORM, providing a clean and consistent interface for common database operations.

Features

  • Type-safe operations: Generic implementation ensures compile-time type safety
  • Transaction support: Built-in transaction management with automatic rollback
  • Flexible querying: Composable scope functions for building complex queries
  • Pagination: Built-in pagination support with configurable page sizes
  • Soft delete support: Full support for GORM's soft delete functionality
  • Row locking: SELECT FOR UPDATE support for concurrent access control
  • Batch operations: Efficient batch insert operations with configurable batch sizes

Installation

go get github.com/nullcache/gorm-plus

Quick Start

package main

import (
    "context"
    "log"

    "github.com/nullcache/gorm-plus"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type User struct {
    ID    uint   `gorm:"primaryKey"`
    Name  string `gorm:"not null"`
    Email string `gorm:"unique;not null"`
    Age   int    `gorm:"default:0"`
}

func main() {
    // Initialize database
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }

    // Auto migrate
    db.AutoMigrate(&User{})

    // Create base model
    userBaseModel, err := gormplus.NewBaseModel[User](db)
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Create a user
    user := &User{Name: "John Doe", Email: "[email protected]", Age: 30}
    err = userBaseModel.Create(ctx, nil, user)
    if err != nil {
        log.Fatal(err)
    }

    // Find user by email
    foundUser, err := userBaseModel.First(ctx, gormplus.Where("email = ?", "[email protected]"))
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Found user: %+v", foundUser)
}

Core Components

Base Model

The BaseModel[T] type is the main component that provides database operations for entities of type T.

// Create a new base model instance
userBaseModel, err := gormplus.NewBaseModel[User](db)

Scopes

Scopes are composable functions that modify GORM queries:

// Basic conditions
users, err := userBaseModel.List(ctx,
    gormplus.Where("age > ?", 25),
    gormplus.Order("name ASC"),
    gormplus.Limit(10),
)

// Map-based conditions
users, err := userBaseModel.List(ctx,
    gormplus.WhereEq(map[string]any{
        "active": true,
        "role":   "admin",
    }),
)

Available Scopes

  • Where(query, args...) - Add WHERE conditions
  • WhereEq(map[string]any) - Add equality conditions from map
  • Order(string) - Add ORDER BY clause
  • Select(columns...) - Select specific columns
  • Limit(int) - Limit number of results
  • Offset(int) - Skip number of results
  • WithDeleted() - Include soft-deleted records
  • OnlyDeleted() - Only soft-deleted records

Operations

CRUD Operations

// Create
user := &User{Name: "Jane Doe", Email: "[email protected]"}
err := userBaseModel.Create(ctx, nil, user)

// Update
user.Age = 25
err = userBaseModel.Update(ctx, nil, user)

// Delete (soft delete if DeletedAt field exists)
err = userBaseModel.Delete(ctx, nil, gormplus.Where("id = ?", user.ID))

Query Operations

// Find first record
user, err := userBaseModel.First(ctx, gormplus.Where("email = ?", "[email protected]"))

// List records
users, err := userBaseModel.List(ctx,
    gormplus.Where("age > ?", 18),
    gormplus.Order("name ASC"),
)

// Count records
count, err := userBaseModel.Count(ctx, gormplus.Where("active = ?", true))

// Check existence
exists, err := userBaseModel.Exists(ctx, gormplus.Where("email = ?", "[email protected]"))

Pagination

// Get paginated results
result, err := userBaseModel.Page(ctx, 1, 20, // page 1, 20 items per page
    gormplus.Where("active = ?", true),
    gormplus.Order("created_at DESC"),
)

// Access pagination info
fmt.Printf("Page: %d/%d, Total: %d, HasNext: %v",
    result.Page,
    (result.Total + int64(result.PageSize) - 1) / int64(result.PageSize),
    result.Total,
    result.HasNext,
)

Batch Operations

users := []*User{
    {Name: "User 1", Email: "[email protected]"},
    {Name: "User 2", Email: "[email protected]"},
    {Name: "User 3", Email: "[email protected]"},
}

// Batch insert with default batch size (1000)
err := userBaseModel.BatchInsert(ctx, nil, users)

// Batch insert with custom batch size
err = userBaseModel.BatchInsert(ctx, nil, users, 100)

Transactions

// Manual transaction management
err := userBaseModel.Transact(ctx, func(ctx context.Context, tx *gorm.DB) error {
    user1 := &User{Name: "User 1", Email: "[email protected]"}
    if err := userBaseModel.Create(ctx, tx, user1); err != nil {
        return err
    }

    user2 := &User{Name: "User 2", Email: "[email protected]"}
    if err := userBaseModel.Create(ctx, tx, user2); err != nil {
        return err
    }

    return nil // Commit transaction
})

// Row locking (requires transaction)
err = db.Transaction(func(tx *gorm.DB) error {
    user, err := userBaseModel.FirstForUpdate(ctx, tx, gormplus.Where("id = ?", 1))
    if err != nil {
        return err
    }

    // Modify user safely
    user.Balance += 100
    return userBaseModel.Update(ctx, tx, &user)
})

Error Handling

The library defines several standard errors:

// Check for specific errors
user, err := userBaseModel.First(ctx, gormplus.Where("id = ?", 999))
if errors.Is(err, gormplus.ErrNotFound) {
    // Handle not found case
}

// Available errors:
// - gormplus.ErrInvalidType: Invalid generic type
// - gormplus.ErrNotFound: Record not found
// - gormplus.ErrTxRequired: Transaction required for operation
// - gormplus.ErrDangerous: Dangerous operation (e.g., delete without conditions)

Best Practices

  1. Always use contexts: Pass context for cancellation and timeout support
  2. Handle transactions properly: Use the Transact method for complex operations
  3. Validate inputs: Check for required conditions before dangerous operations
  4. Use appropriate scopes: Combine scopes to build precise queries
  5. Handle errors: Always check for specific error types when needed

Requirements

  • Go 1.19 or higher
  • GORM v1.25.0 or higher

License

MIT License. See LICENSE file for details.

Contributing

Contributions are welcome. Please ensure all tests pass and follow the existing code style.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages