Cursor rule
.cursor/rules/thing.mdc[object Object]
Cursor rules
Quality
58/100
Scores the file, not the repository.Length
1,156 words
8 headings · 14 code blocksRepository
406
— · pushed 1 days agoLast changed
3 days ago
First indexed 3 days ago.123456---7description:8globs: *.go9alwaysApply: false10---11# Rule Name: Thing ORM Usage Guide1213# Description:14# Quick reference for using the Thing ORM (github.com/burugo/thing).1516## Core Concept17- High-performance Go ORM focusing on CRUD and list operations.18- Built-in caching (Redis or in-memory) automatically handles single-entity and list query caching & invalidation.19- Uses Go generics for type safety and a cleaner API.20- Supports MySQL, PostgreSQL, SQLite.2122## Configuration231. **Create DB Adapter:**24```go25 import "github.com/burugo/thing/drivers/db/sqlite" // Or mysql, postgres26 dbAdapter, err := sqlite.NewSQLiteAdapter(":memory:")27```282. **Create Cache Client (Optional, defaults to in-memory):**29```go30 import (31 "github.com/redis/go-redis/v9"32 redisCache "github.com/burugo/thing/drivers/cache/redis"33 "github.com/burugo/thing"34 )35 // Use nil for default in-memory:36 // var cacheClient thing.CacheClient = nil37 // Or Redis:38 // rdb := redis.NewClient(...)39 // cacheClient = redisCache.NewClient(rdb)40```413. **Get ORM Instance per Model:**42```go43 import "github.com/burugo/thing"44 // import your models package4546 // For a specific model typels.User)47 userThing, err := thing.New[*models.User](mdc:db)48```49 *Alternative Global Config (less flexible):* `thing.Configure(dbAdapter, cacheClient)` then `thing.Use[*Model]()`.5051## Basic Usage521. **Model Definition:** Embed `thing.BaseModel` and use `db` tags for columns.53```go54 type User struct {55 thing.BaseModel // Includes ID, CreatedAt, UpdatedAt, DeletedAt56 Name string `db:"name"` // Explicitly maps Name field to 'name' column57 UserAge int // db tag omitted, defaults to 'user_age' column58 }59 // Optional: Define TableName() method if different from snake_case plural struct name.60 // func (u *User) TableName() string { return "custom_users_table" }61```62 **Note:** While using the `db` tag is recommended for clarity, it's technically optional. If omitted, Thing ORM defaults to converting the Go field name (CamelCase, e.g., `UserAge`) to its snake_case equivalent (`user_age`) as the database column name. Use `db:"-"` to explicitly ignore a field.6364 **Example:**65```go66 // Example: All index types and relationships67 type User struct {68 thing.BaseModel69 Name string `db:"name,index"` // Single-field index70 Email string `db:"email,unique"` // Single-field unique index71 ColA string `db:"col_a,index:idx_ab"` // Composite index (idx_ab)72 ColB int `db:"col_b,index:idx_ab"` // Composite index (idx_ab)73 KeyA string `db:"key_a,unique:uq_ab"` // Composite unique index (uq_ab)74 KeyB string `db:"key_b,unique:uq_ab"` // Composite unique index (uq_ab)75 // Relationship example:76 Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`77 }78```7980 **Indexes are always declared in the db tag (index, unique, index:..., unique:...).**81 **The thing tag is only used for relationship declarations (hasMany, belongsTo, model, fk, etc.).**82 **Do not use thing tag for index declaration.**832. **CRUD Operations and Querying (using `userThing` from config step):**84 - **Create/Update:** `err := userThing.Save(&userInstance)` (creates if ID is zero, updates otherwise).85 - **Read by ID:** `foundUser, err := userThing.ByID(id)` (uses cache).86 - **Read Multiple by IDs:** `userMap, err := userThing.ByIDs([]int64{1, 2, 3}, "OptionalPreloadField")` (uses cache, returns `map[int64]YourModelType`. Preloading is optional).87 - **Delete (Hard):** `err := userThing.Delete(&userInstance)`.88 - **Soft Delete:** `err := userThing.SoftDelete(&userInstance)` (sets `deleted=true` and `updated_at`).8990 - **Querying Records:**91 The `thing` ORM provides a flexible way to build and execute queries.9293 - **Initiating a Query:**94 You can start a query using `Query()` with `thing.QueryParams` or by chaining methods like `Where()`, `Order()`, and `Preload()`. All query methods return a `*CachedResult[T]` instance, allowing for lazy execution.95```go96 // Option 1: Using QueryParams97 params := thing.QueryParams{98 Where: "age > ? AND status = ?", Args: []interface{}{25, "active"},99 Order: "name ASC",100 Preloads: []string{"Books"}, // For preloading relationships101 }102 result := userThing.Query(params)103104 // Option 2: Chainable methods (can be started from userThing or chained on a CachedResult)105 // Starting from userThing:106 result = userThing.Where("age > ?", 25).Order("name ASC").Preload("Books")107 // Chaining on an existing result:108 // result = existingResult.Where("status = ?", "active")109```110111 - **Fetching Results from `CachedResult[T]`:**112 Once you have a `CachedResult[T]`, you can fetch data in various ways:113```go114 // Fetch a paginated list (e.g., offset 0, limit 10)115 users, err := result.Fetch(0, 10) // Uses cache116117 // Fetch the first record matching the query118 // Returns common.ErrNotFound if no record matches119 firstUser, err := result.First()120 if err != nil {121 if errors.Is(err, common.ErrNotFound) {122 // Handle case where no user was found123 } else {124 // Handle other errors125 }126 }127128 // Fetch all records matching the query129 allMatchingUsers, err := result.All()130```131132 - **Counting Records:**133 To get the total number of records matching the query conditions:134```go135 count, err := result.Count() // Uses cache136```137138 - **Including Soft-Deleted Records:**139 By default, queries exclude soft-deleted records. To include them, use `WithDeleted()` on a `CachedResult[T]`:140```go141 resultIncludingDeleted := result.WithDeleted()142 // Now, Fetch(), First(), All(), Count() on resultIncludingDeleted will include soft-deleted items.143 // Example:144 // usersIncludingDeleted, err := resultIncludingDeleted.Fetch(0, 10)145```146147 - **Fetching All Records of a Model (Simplified):**148 To get all records for a model type without any specific query conditions:149```go150 allUsersOfType, err := userThing.All()151 // This is a convenient alias for userThing.Query(thing.QueryParams{}).All()152```153154## Key Features155- **Caching:** Mostly automatic for `ByID` and `Query`. Monitor via `cacheClient.GetCacheStats(ctx)`.156- **Relationships:** Define using `thing` struct tags (`hasMany`, `belongsTo`, `manyToMany`) and preload using `QueryParams.Preloads`. Always use `db:"-"` on relation fields in your struct, as these fields are populated by the ORM and do not correspond directly to a column in the model's own table.157158 - **`hasMany`**: One-to-many relationship.159```go160 // User has many Books.161 // Assumes 'books' table has a 'user_id' foreign key.162 type User struct {163 thing.BaseModel164 // ... other fields165 Books []*Book `thing:"hasMany;fk:user_id;model:Book" db:"-"`166 }167168 type Book struct {169 thing.BaseModel170 UserID uint `db:"user_id"` // Foreign key171 Title string `db:"title"`172 // ... other fields173 }174```175176 - **`belongsTo`**: Inverse of one-to-many.177```go178 // Book belongs to a User.179 // Assumes 'books' table has a 'user_id' foreign key.180 type Book struct {181 thing.BaseModel182 UserID uint `db:"user_id"` // Foreign key183 Title string `db:"title"`184 User *User `thing:"belongsTo;fk:user_id;model:User" db:"-"` // 'model' is optional here if type matches185 // ... other fields186 }187188 type User struct {189 thing.BaseModel190 Name string `db:"name"`191 // ... other fields192 }193```194195 - **`manyToMany`**: Many-to-many relationship.196 Requires a join table.197```go198 // User has and belongs to many Roles.199 // Roles has and belongs to many Users.200 // Join table: 'user_roles' with 'user_id' (links to users.id) and 'role_id' (links to roles.id).201 type User struct {202 thing.BaseModel203 Name string `db:"name"`204 Roles []*Role `thing:"manyToMany;model:Role;joinTable:user_roles;joinLocalKey:user_id;joinRelatedKey:role_id" db:"-"`205 // ... other fields206 }207208 type Role struct {209 thing.BaseModel210 Name string `db:"name"`211 Users []*User `thing:"manyToMany;model:User;joinTable:user_roles;joinLocalKey:role_id;joinRelatedKey:user_id" db:"-"`212 // ... other fields213 }214215 // Join table model (optional to define as a struct, but table must exist)216 // type UserRole struct {217 // UserID uint `db:"user_id"`218 // RoleID uint `db:"role_id"`219 // }220 // func (ur *UserRole) TableName() string { return "user_roles" }221```222 Preloading relationships:223```go224 // Preload Books for a User225 // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Books"}}).Fetch(0,10)226227 // Preload Roles for a User (manyToMany)228 // users, err := userThing.Query(thing.QueryParams{Preloads: []string{"Roles"}}).Fetch(0,10)229```230- **Hooks:** Register listeners for events (e.g., `BeforeSave`, `AfterCreate`) using `thing.RegisterListener`.231- **Auto Migration:** `thing.AutoMigrate(&User{}, &Book{})` creates/updates tables based on model struct tags (`db`).232- **JSON Serialization:** `thing.ToJSON(model, options...)`. Use `thing.WithFields("field1,nested{subfield},-excluded")` for flexible control, including method-based virtual properties (e.g., `FullName() string` -> `full_name` in DSL).233- **Raw SQL:** Access via `dbAdapter := userThing.DBAdapter()`, then use `dbAdapter.Exec`, `dbAdapter.Get`, `dbAdapter.Select`.234235## Focus236- Optimized for common CRUD and list retrieval patterns.237- Complex JOINs or database-specific features might require Raw SQL.
Also in burugo/one-mcp
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| burugo/one-mcp.cursor/rules/language.mdc · 406 | Cursor rules | git | 4/100 | 3 days ago | |
| burugo/one-mcp.cursor/rules/tdd.mdc · 406 | Cursor rules | testlint-formattesting-strategyapi+1 | 89/100 | 3 days ago | |
| burugo/one-mcpAGENTS.md · 406 | AGENTS.md | setupbuildtestlint-format+4 | 79/100 | 3 days ago |
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
