-
-
Notifications
You must be signed in to change notification settings - Fork 786
Add solution for gorm challenge-2-associations by aswinsreeraj #853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add solution for gorm challenge-2-associations by aswinsreeraj #853
Conversation
WalkthroughA new submission file for a GORM-based ORM layer implementing a blog system with User, Post, and Tag models and their associations. Provides database connection setup and CRUD utility functions for managing users, posts, tags, and their relationships using SQLite and GORM. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Pre-merge checks✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go (5)
41-41: Remove or update stale TODO comments now that implementations existEach of these TODOs has already been implemented below the comment. Leaving them in place is misleading; consider deleting them or turning them into brief doc comments if you want to keep context.
Also applies to: 52-52, 59-59, 67-67, 81-81, 92-92, 110-110
39-48: ConnectDB correctly opens SQLite and auto‑migrates modelsThe connection setup and
AutoMigratecall look good for this challenge context. If you ever reuse this in a non‑kata setting, consider making the DSN ("test.db") configurable rather than hard‑coding it.
50-55: CreateUserWithPosts is fine; could be slightly simplifiedUsing
db.Create(user)is appropriate here and will persist associated posts when present. You can tighten this to a single expression if you like:-func CreateUserWithPosts(db *gorm.DB, user *User) error { - result := db.Create(user) - return result.Error -} +func CreateUserWithPosts(db *gorm.DB, user *User) error { + return db.Create(user).Error +}
57-63: Consider returning nil user when an error occursRight now, you return a non‑nil
*Usereven whenresult.Erroris non‑nil. It’s more idiomatic (and less surprising to callers) to returnnilon error:func GetUserWithPosts(db *gorm.DB, userID uint) (*User, error) { - var user User - result := db.Preload("Posts").First(&user, userID) - return &user, result.Error + var user User + result := db.Preload("Posts").First(&user, userID) + if result.Error != nil { + return nil, result.Error + } + return &user, nil }
79-88: GetPostsByTag query is correct; consider setting the base model explicitlyThe join logic and filter by
tags.namelook good and will retrieve the desired posts. For clarity and to guard against future changes to the default model, you could make the base model explicit:- err := db.Joins("JOIN post_tags ON posts.id = post_tags.post_id"). - Joins("JOIN tags ON post_tags.tag_id = tags.id"). - Where("tags.name = ?", tagName). - Find(&posts).Error + err := db.Model(&Post{}). + Joins("JOIN post_tags ON posts.id = post_tags.post_id"). + Joins("JOIN tags ON post_tags.tag_id = tags.id"). + Where("tags.name = ?", tagName). + Find(&posts).ErrorIf there’s any chance of duplicates due to more complex joins in the future, adding
Distinct("posts.*")would also be an easy safeguard.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go(1 hunks)
🔇 Additional comments (3)
packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go (3)
10-37: User/Post/Tag models and associations are well-structuredThe primary keys, uniqueness constraints, and has‑many / many‑to‑many relationships are modeled cleanly and match typical GORM conventions for this kind of blog domain.
108-117: GetPostWithUserAndTags cleanly preloads relations and returns a clear errorPreloading both
UserandTagsthen returningnil, erron failure is straightforward and idiomatic. This is a good pattern to mirror in other retrieval helpers.
90-106: PropagateFirstOrCreateerrors when adding tags to an existing postThe
AddTagsToPostfunction ignores potential DB errors fromFirstOrCreate, which can lead to silent failures while still returningnil. Always check the.Errorfield after GORM finisher methods:for _, tagName := range tagNames { var tag Tag - db.FirstOrCreate(&tag, Tag{Name: tagName}) - if err := db.Model(&post).Association("Tags").Append(&tag); err != nil { - return err - } + if err := db.FirstOrCreate(&tag, Tag{Name: tagName}).Error; err != nil { + return err + } + if err := db.Model(&post).Association("Tags").Append(&tag); err != nil { + return err + } } return nilConsider wrapping the function body in a transaction to prevent partially applied tag sets if an error occurs mid-operation.
| // CreatePostWithTags creates a new post with specified tags | ||
| func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error { | ||
| // TODO: Implement post creation with tags | ||
| if err := db.Create(post).Error; err != nil { | ||
| return err | ||
| } | ||
| for _, name := range tagNames { | ||
| var tag Tag | ||
| db.FirstOrCreate(&tag, Tag{Name: name}) | ||
| db.Model(post).Association("Tags").Append(&tag) | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -path "*packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go" -type fRepository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
fd "solution.go" packages/gorm/challenge-2-associations/submissions/aswinsreeraj/Repository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
find . -type f -name "solution.go" | grep aswinsreerajRepository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
git ls-files | grep -i aswinsreerajRepository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
ls -la packages/gorm/challenge-2-associations/submissions/ 2>/dev/null || echo "Directory not found"Repository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
fd -H "solution.go" --max-one-resultRepository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
find . -type f -name "*.go" | head -20Repository: RezaSi/go-interview-practice
Length of output: 200
🏁 Script executed:
git ls-files | head -50Repository: RezaSi/go-interview-practice
Length of output: 200
🌐 Web query:
GORM FirstOrCreate error handling return value
💡 Result:
Short answer — check result.Error for DB errors and use result.RowsAffected to tell if a create/update happened.
Details and example:
- GORM (traditional API) sets any operation error on the returned *gorm.DB: check result.Error (or result := db.Where(...).FirstOrCreate(&m); if result.Error != nil { … }). [1][2]
- FirstOrCreate will return a result whose RowsAffected indicates how many rows were created/updated by that call. If RowsAffected > 0 the call caused an insert/update (i.e. a new row created or fields assigned/updated); if 0 no write was performed because an existing row was returned. Use RowsAffected to distinguish “found” vs “created/updated”. [1][3][4]
Example:
- result := db.Where(User{Name: "x"}).Attrs(User{Age:20}).FirstOrCreate(&user)
- if result.Error != nil { /* handle DB error */ }
- if result.RowsAffected > 0 { /* created or updated / } else { / already existed (no write) */ }
Sources: GORM docs — FirstOrCreate / Error handling and examples. [1][2][3]
References
- GORM docs — FirstOrCreate / Advanced Query. [1]
- GORM docs — Error handling. [2]
- GORM examples and community notes (RowsAffected behaviour). [3][4]
🌐 Web query:
GORM Association Append error handling
💡 Result:
Short answer — check the Association's Error field (or the returned error for the Generics API) and use transactions for multi-step/atomic work.
Examples and notes
-
Traditional (chainable) API — Append returns *Association; check .Error:
- db.Model(&user).Association("Languages").Append(&lang)
- if err := db.Model(&user).Association("Languages").Error; err != nil { /* handle */ }
- or: if assoc := db.Model(&user).Association("Languages").Append(&lang); assoc.Error != nil { /* handle assoc.Error */ }
-
Generics API (v2 newer style) — methods return error directly; check the returned error:
- if err := gorm.GUser.Model(&user).Association("Languages").Append(ctx, &lang); err != nil { /* handle */ }
-
Use db.Transaction when you need atomicity across multiple operations:
- return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&user).Association("Languages").Append(&lang).Error; err != nil { return err }
// other ops...
return nil
})
- return db.Transaction(func(tx *gorm.DB) error {
-
Common gotchas:
- Association operations set the Association.Error field — always read that after Append/Replace/Delete/Find. [1][2]
- Batch Append/Replace requires argument lengths to match when using batch mode; mismatched lengths will produce an error. [4]
- Many-to-many join-table constraint failures, missing primary key on source model, or invalid association name are frequent causes of errors — check the exact error message and use transactions if you need rollback behavior. [3][5]
References
- GORM Associations docs (Append / Association.Error). [1]
- GORM Error handling (traditional + generics API). [2]
- GORM Associations / batch mode & requirements. [4]
- GORM transaction recommendation for multi-step operations. [3]
References mapping:
[1] https://gorm.io/docs/associations.html
[2] https://gorm.io/docs/error_handling.html
[3] https://gorm.io/docs/transactions.html
[4] https://learnku.com/docs/gorm/v2/associations/9740
[5] https://pkg.go.dev/github.com/let-s-go/gorm
🌐 Web query:
GORM v2 FirstOrCreate documentation error
💡 Result:
Short answer — the GORM v2 docs/examples can be misleading: FirstOrCreate (and FirstOrInit) only use struct/map conditions for the lookup; string/SQL-style Where conditions may be ignored when passed as the "where" argument to FirstOrCreate. Use struct or map conditions (or call Where(...) then FirstOrCreate with a struct) or provide create attrs via the second parameter (or Attrs/Assign). Sources and examples: [1][2][3].
- Blog demonstrating the behaviour and workaround. [1]
- GORM docs FirstOrCreate description / examples (showing struct/map usage and Attrs/Assign). [2]
- pkg.go.dev entry that explicitly states "only works with struct, map conditions". [3]
References
[1] Lessons Learned: Golang’s GORM FirstOrCreate — David Vassallo.
[2] GORM docs — Query / FirstOrCreate (Attrs / Assign examples).
[3] pkg.go.dev: FirstOrCreate description (notes only struct/map conditions).
🌐 Web query:
GORM v2 Association Append Error method
💡 Result:
Short answer: In official go-gorm/gorm (v2) Association.Append returns an *Association (whose Error field holds any error). Do not use .Error after Append as if it returned a *gorm.DB — instead either read the returned Association.Error or call Association(...).Error before/after. Some forks or older examples differ (some implementations returned error directly), so you may see both patterns in the wild.
Examples:
- official pattern (check returned Association.Error): assoc := db.Model(&user).Association("Languages").Append(&lang); err := assoc.Error. [1]
- alternate pattern seen in some answers/forks: err := db.Model(&user).Association("Languages").Append(&lang) — only valid if that fork's Append returns error. [2]
References:
- GORM associations docs (showing Association API and Error field). [1]
- StackOverflow discussion noting differences when migrating / some implementations returning error directly. [2]
Sources:
[1] GORM Associations docs — gorm.io.
[2] StackOverflow — "Getting 'Missing Error method' when migrating from GORM V1 to V2".
Handle errors from FirstOrCreate and appending tag associations
The CreatePostWithTags function ignores DB errors from FirstOrCreate and Association("Tags").Append, allowing the function to return success even if tags fail to persist. Both operations in GORM v2 require error checking via the .Error field:
func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error {
if err := db.Create(post).Error; err != nil {
return err
}
for _, name := range tagNames {
var tag Tag
- db.FirstOrCreate(&tag, Tag{Name: name})
- db.Model(post).Association("Tags").Append(&tag)
+ if err := db.FirstOrCreate(&tag, Tag{Name: name}).Error; err != nil {
+ return err
+ }
+ if err := db.Model(post).Association("Tags").Append(&tag).Error; err != nil {
+ return err
+ }
}
return nil
}For full atomicity (rollback on failure), wrap the operation in a transaction using db.Transaction().
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // CreatePostWithTags creates a new post with specified tags | |
| func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error { | |
| // TODO: Implement post creation with tags | |
| if err := db.Create(post).Error; err != nil { | |
| return err | |
| } | |
| for _, name := range tagNames { | |
| var tag Tag | |
| db.FirstOrCreate(&tag, Tag{Name: name}) | |
| db.Model(post).Association("Tags").Append(&tag) | |
| } | |
| return nil | |
| } | |
| // CreatePostWithTags creates a new post with specified tags | |
| func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error { | |
| // TODO: Implement post creation with tags | |
| if err := db.Create(post).Error; err != nil { | |
| return err | |
| } | |
| for _, name := range tagNames { | |
| var tag Tag | |
| if err := db.FirstOrCreate(&tag, Tag{Name: name}).Error; err != nil { | |
| return err | |
| } | |
| if err := db.Model(post).Association("Tags").Append(&tag).Error; err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } |
🤖 Prompt for AI Agents
In packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go
around lines 65-77, the function ignores errors from FirstOrCreate and
Association(...).Append and doesn't run atomically; change the implementation to
run inside a transaction (use db.Transaction(func(tx *gorm.DB) error { ... }))
and within it: use tx.Create(post).Error and check its error, use
tx.FirstOrCreate(&tag, Tag{Name: name}).Error and return any error, and check
the error returned by tx.Model(post).Association("Tags").Append(&tag) (return it
on failure) so any failure rolls back; finally return nil on success.
gorm challenge-2-associations Solution
Submitted by: @aswinsreeraj
Package: gorm
Challenge: challenge-2-associations
Description
This PR contains my solution for gorm challenge-2-associations.
Changes
packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.goTesting
Thank you for reviewing my submission! 🚀