Skip to content

Conversation

@aswinsreeraj
Copy link
Contributor

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

  • Added solution file to packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go

Testing

  • Solution passes all test cases
  • Code follows Go best practices

Thank you for reviewing my submission! 🚀

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

A 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

Cohort / File(s) Change Summary
GORM Blog ORM Submission
packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go
Introduces User, Post, and Tag models with GORM associations (one-to-many User↔Post, many-to-many Post↔Tag). Implements ConnectDB for database setup and auto-migration, CreateUserWithPosts and GetUserWithPosts for user CRUD, CreatePostWithTags and AddTagsToPost for post-tag operations, GetPostsByTag for querying posts by tag, and GetPostWithUserAndTags for fetching complete post data with relations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify GORM association definitions (one-to-many and many-to-many relationships) are correctly configured with proper struct tags and foreign keys
  • Review error handling in each function and ensure errors are properly propagated
  • Validate Preload, Joins, and FirstOrCreate usage patterns match GORM best practices
  • Confirm the auto-migration in ConnectDB will produce the intended database schema with correct constraints

Possibly related PRs

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a solution for the gorm challenge-2-associations by aswinsreeraj.
Description check ✅ Passed The description is directly related to the changeset, explaining the submission of a gorm challenge solution with appropriate context and change details.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 exist

Each 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 models

The connection setup and AutoMigrate call 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 simplified

Using 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 occurs

Right now, you return a non‑nil *User even when result.Error is non‑nil. It’s more idiomatic (and less surprising to callers) to return nil on 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 explicitly

The join logic and filter by tags.name look 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).Error

If 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

📥 Commits

Reviewing files that changed from the base of the PR and between f761321 and 9f7c2f3.

📒 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-structured

The 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 error

Preloading both User and Tags then returning nil, err on failure is straightforward and idiomatic. This is a good pattern to mirror in other retrieval helpers.


90-106: Propagate FirstOrCreate errors when adding tags to an existing post

The AddTagsToPost function ignores potential DB errors from FirstOrCreate, which can lead to silent failures while still returning nil. Always check the .Error field 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 nil

Consider wrapping the function body in a transaction to prevent partially applied tag sets if an error occurs mid-operation.

Comment on lines +65 to +77
// 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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -path "*packages/gorm/challenge-2-associations/submissions/aswinsreeraj/solution.go" -type f

Repository: 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 aswinsreeraj

Repository: RezaSi/go-interview-practice

Length of output: 200


🏁 Script executed:

git ls-files | grep -i aswinsreeraj

Repository: 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-result

Repository: RezaSi/go-interview-practice

Length of output: 200


🏁 Script executed:

find . -type f -name "*.go" | head -20

Repository: RezaSi/go-interview-practice

Length of output: 200


🏁 Script executed:

git ls-files | head -50

Repository: 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

  1. GORM docs — FirstOrCreate / Advanced Query. [1]
  2. GORM docs — Error handling. [2]
  3. 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
      })
  • 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.

Suggested change
// 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.

@RezaSi RezaSi merged commit 04dd646 into RezaSi:main Dec 8, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants