Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"time"

"gorm.io/gorm"
"gorm.io/driver/sqlite"
)

// User represents a user in the blog system
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"not null"`
Email string `gorm:"unique;not null"`
Posts []Post `gorm:"foreignKey:UserID"`
CreatedAt time.Time
UpdatedAt time.Time
}

// Post represents a blog post
type Post struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"not null"`
Content string `gorm:"type:text"`
UserID uint `gorm:"not null"`
User User `gorm:"foreignKey:UserID"`
Tags []Tag `gorm:"many2many:post_tags;"`
CreatedAt time.Time
UpdatedAt time.Time
}

// Tag represents a tag for categorizing posts
type Tag struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"unique;not null"`
Posts []Post `gorm:"many2many:post_tags;"`
}

// ConnectDB establishes a connection to the SQLite database and auto-migrates the models
func ConnectDB() (*gorm.DB, error) {
// TODO: Implement database connection with auto-migration
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
return nil, err
}
err = db.AutoMigrate(&User{}, &Post{}, &Tag{})
return db, err
}

// CreateUserWithPosts creates a new user with associated posts
func CreateUserWithPosts(db *gorm.DB, user *User) error {
// TODO: Implement user creation with posts
result := db.Create(user)
return result.Error
}

// GetUserWithPosts retrieves a user with all their posts preloaded
func GetUserWithPosts(db *gorm.DB, userID uint) (*User, error) {
// TODO: Implement user retrieval with posts
var user User
result := db.Preload("Posts").First(&user, userID)
return &user, result.Error
}

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


// GetPostsByTag retrieves all posts that have a specific tag
func GetPostsByTag(db *gorm.DB, tagName string) ([]Post, error) {
// TODO: Implement posts retrieval by tag
var posts []Post
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
return posts, err
}

// AddTagsToPost adds tags to an existing post
func AddTagsToPost(db *gorm.DB, postID uint, tagNames []string) error {
// TODO: Implement adding tags to existing post
var post Post
result := db.First(&post, postID)
if result.Error != nil {
return result.Error
}
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
}
}
return nil
}

// GetPostWithUserAndTags retrieves a post with user and tags preloaded
func GetPostWithUserAndTags(db *gorm.DB, postID uint) (*Post, error) {
// TODO: Implement post retrieval with user and tags
var post Post
err := db.Preload("User").Preload("Tags").First(&post, postID).Error
if err != nil {
return nil, err
}
return &post, nil
}
Loading