Skip to content

Conversation

@grozdovk
Copy link
Contributor

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

Adds three new Go submissions: a palindrome checker (challenge-17), a generics utilities package (challenge-27) with multiple data structures, and two GORM-backed services for challenges (challenge-1 CRUD and challenge-2 associations) including models, relations, and transactional operations.

Changes

Cohort / File(s) Change Summary
Challenge-17 — Palindrome Checker
challenge-17/submissions/grozdovk/solution-template.go
Adds IsPalindrome(s string) bool (lowercases, removes non-alphanumeric, reverses runes with two-pointer approach) and a main that reads stdin and prints palindrome result.
Challenge-27 — Generics Package
challenge-27/submissions/grozdovk/solution-template.go
Introduces generics package: Pair, Stack, Queue, Set (with comparable constraint), ErrEmptyCollection, constructors and methods, and utilities Filter, Map, Reduce, Contains, FindIndex, RemoveDuplicates.
GORM — CRUD (challenge-1)
packages/gorm/challenge-1-crud-operations/submissions/grozdovk/solution.go
Adds User model (ID, Name, Email unique, Age, timestamps), ConnectDB() (sqlite + AutoMigrate) and CRUD functions: CreateUser, GetUserByID, GetAllUsers, UpdateUser, DeleteUser.
GORM — Associations (challenge-2)
packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go
Adds User, Post, Tag models with relations (one-to-many, many-to-many via post_tags), ConnectDB(), and relation-aware functions: CreateUserWithPosts, GetUserWithPosts, CreatePostWithTags (transactional), GetPostsByTag, AddTagsToPost (transactional), GetPostWithUserAndTags.

Sequence Diagram(s)

Simple flow for the palindrome checker (normalize → reverse → compare):

sequenceDiagram
  participant CLI as User/CLI
  participant App as PalindromeApp
  CLI->>App: input string
  App->>App: normalize (lowercase, keep a-z0-9)
  App->>App: reverse runes (two-pointer)
  App->>App: compare normalized == reversed
  App-->>CLI: print result (palindrome / not palindrome)
Loading

High-level transactional flow for CreatePostWithTags / AddTagsToPost (GORM many-to-many with transaction):

sequenceDiagram
  autonumber
  participant Caller as Caller
  participant DB as GORM (sqlite)
  Caller->>DB: Begin Transaction
  DB-->>Caller: tx
  Caller->>DB: ensure/create Tag rows (for each tag)
  DB-->>Caller: tags persisted
  Caller->>DB: create Post (or find Post)
  DB-->>Caller: post persisted
  Caller->>DB: associate Tags <-> Post (Append to many2many)
  DB-->>Caller: associations persisted
  alt success
    Caller->>DB: Commit
    DB-->>Caller: Commit OK
  else error
    Caller->>DB: Rollback
    DB-->>Caller: Rollback OK
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing extra attention:
    • Transaction correctness and error handling in CreatePostWithTags and AddTagsToPost (rollback/commit paths).
    • Uniqueness and constraint handling for Tag and User.Email (possible race conditions or constraint errors).
    • Generic API correctness in generics package (type parameter constraints, error sentinel use, zero-value handling in Pop/Dequeue).
    • Rune handling and normalization edge-cases in IsPalindrome (Unicode, non-ASCII alphanumerics).

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'gorm challenge-1-crud-operations' but the changeset includes solutions for 4 different challenges: challenge-17, challenge-27, gorm challenge-1, and gorm challenge-2. Update the title to accurately reflect all changes in the PR, such as 'Add multiple solutions for challenges and gorm operations' or split into separate PRs per challenge.
Description check ❓ Inconclusive No description was provided; however, the changeset contains multiple unrelated solutions across different challenges. Add a comprehensive description explaining why multiple solutions are bundled together and clarifying which challenges are being addressed.
✅ Passed checks (1 passed)
Check name Status Explanation
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: 0

🧹 Nitpick comments (10)
challenge-17/submissions/grozdovk/solution-template.go (3)

8-21: Consider using a more flexible input method.

fmt.Scanln stops reading at the first whitespace, so multi-word inputs like "A man a plan a canal Panama" would only process "A". For comprehensive palindrome testing, consider using bufio.Scanner or fmt.Scan with a full line read.

Example refactor:

+import (
+	"bufio"
 	"fmt"
+	"os"
 	"strings"
+)

 func main() {
 	// Get input from the user
 	var input string
 	fmt.Print("Enter a string to check if it's a palindrome: ")
-	fmt.Scanln(&input)
+	scanner := bufio.NewScanner(os.Stdin)
+	if scanner.Scan() {
+		input = scanner.Text()
+	}

26-27: Remove the TODO comment.

The function is fully implemented, so the TODO comment should be removed.

-	// TODO: Implement this function
-	// 1. Clean the string (remove spaces, punctuation, and convert to lowercase)

28-42: Implementation is correct.

The palindrome logic correctly handles case insensitivity and filters non-alphanumeric characters. The two-step approach (clean then reverse-and-compare) works well.

Optional: You could optimize by using a two-pointer comparison instead of creating a reversed copy:

// After getting cleanedString and runes:
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
    if runes[i] != runes[j] {
        return false
    }
}
return true

This avoids allocating memory for the reversed string.

challenge-27/submissions/grozdovk/solution-template.go (6)

12-34: Pair implementation looks good.

The generic Pair struct and its methods are correctly implemented. Remember to remove the TODO comments on lines 20 and 29 since the implementation is complete.


40-97: Stack implementation is correct.

All Stack methods (Push, Pop, Peek, Size, IsEmpty) are properly implemented with appropriate error handling for empty stack operations.

Minor formatting inconsistencies on lines 65, 79, and 96: add spaces around the == operator for consistency with Go conventions (e.g., len(s.elements) == 0). Also, remove the TODO comments since the implementation is complete.


103-158: Queue implementation is functionally correct.

All Queue methods work as expected with proper error handling for empty queue operations.

Optional improvements:

  • Add spaces around == operators (lines 128, 141, 157) for consistency
  • Remove TODO comments since implementation is complete
  • Consider that Dequeue at line 132 using q.elements[1:] can lead to memory retention in long-running scenarios, as the underlying array keeps references to dequeued elements. For production code, periodically copying to a fresh slice when the queue is smaller than the capacity would help, but for a learning exercise this is fine.

164-211: Set implementation and operations are correct.

The Set data structure and all its methods work properly. Union, Intersection, and Difference operations are implemented correctly.

Optional improvements:

  • Consider using map[T]struct{} instead of map[T]any (line 167) to save memory, as you only need the keys
  • In Elements() (line 206), preallocate the slice with capacity: make([]T, 0, len(s.elements)) for better performance
  • Add spaces around comparison operators for consistency with Go style
  • Remove TODO comments

213-254: Set operations are correctly implemented.

Union, Intersection, and Difference operations correctly handle the set algebra. The logic for each operation is sound.

Remember to remove the TODO comments on lines 215, 230, and 244.


260-325: Generic utility functions are well implemented.

All utility functions (Filter, Map, Reduce, Contains, FindIndex, RemoveDuplicates) correctly implement their expected behaviors. The use of generics makes these reusable across different types.

Minor items:

  • Remove TODO comments (lines 262, 274, 284, 293, 304, 315)
  • Add spaces around comparison operators for consistency
packages/gorm/challenge-1-crud-operations/submissions/grozdovk/solution.go (1)

38-84: CRUD helpers are correct; consider simplifying existence checks using RowsAffected

The CRUD functions are logically correct and propagate errors cleanly. CreateUser, GetUserByID, and GetAllUsers are already minimal and fine.

For UpdateUser and DeleteUser, you’re doing a First followed by Save/Delete. That enforces a “must exist” contract but costs an extra query and still has a small time‑of‑check/time‑of‑use gap. If you don’t strictly need a separate pre‑check, you could instead rely on RowsAffected to detect missing rows after a single operation, e.g.:

  • For updates: run a single Update/Updates and return gorm.ErrRecordNotFound (or similar) when RowsAffected == 0.
  • For deletes: run a single Delete and treat RowsAffected == 0 as “user not found”.

This would reduce DB round‑trips while keeping clear not‑found semantics, at the cost of slightly different GORM behavior (e.g., Updates vs Save regarding zero‑values), so worth aligning with the challenge requirements before changing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca8ea29 and ebcf025.

📒 Files selected for processing (3)
  • challenge-17/submissions/grozdovk/solution-template.go (1 hunks)
  • challenge-27/submissions/grozdovk/solution-template.go (1 hunks)
  • packages/gorm/challenge-1-crud-operations/submissions/grozdovk/solution.go (1 hunks)
🔇 Additional comments (2)
packages/gorm/challenge-1-crud-operations/submissions/grozdovk/solution.go (2)

12-20: User model definition is clear and appropriately constrained

The struct captures primary key, uniqueness, not‑null, and positive age constraints cleanly and is a good fit for the CRUD exercise.


22-36: Proposed fix is unsafe and introduces new bugs — revise error handling strategy

The web search reveals critical issues with the proposed fix:

  1. GORM ownership model: When you pass *sql.DB via sqlite.Dialector{Conn: sqlDB}, you (the caller) remain responsible for its lifecycle; GORM does not take ownership.

  2. Unsafe to close sqlDB while gorm.DB is active: Closing the underlying *sql.DB while the returned *gorm.DB still references it shuts down the connection pool and causes GORM operations to fail. With SQLite, this can also trigger SQLITE_BUSY errors.

  3. Critical flaw in the proposed fix: The diff closes sqlDB in the AutoMigrate() error branch (_ = sqlDB.Close(); return nil, err), but the original code returns db, err on that failure. This means closing sqlDB while the caller expects to receive a live *gorm.DB — the connection will be unusable.

The actual resource leak occurs only on gorm.Open() failure, where sqlDB is opened but never closed. The AutoMigrate() failure case is not a leak because the function returns db to the caller, who owns cleanup responsibility via db.DB().Close() at shutdown.

Corrected approach: Close sqlDB only on gorm.Open() failure:

 func ConnectDB() (*gorm.DB, error) {
 	sqlDB, err := sql.Open("sqlite", "test.db")
 	if err != nil {
 		return nil, err
 	}
 	
 	db, err := gorm.Open(sqlite.Dialector{Conn: sqlDB}, &gorm.Config{})
 	if err != nil {
+		_ = sqlDB.Close()
 		return nil, err
 	}
 	
 	err = db.AutoMigrate(&User{})
 	return db, err
 }

The caller must ensure db.DB().Close() is called at graceful shutdown; do not close sqlDB here on AutoMigrate() failure.

Likely an incorrect or invalid review comment.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebcf025 and 072e0e8.

📒 Files selected for processing (1)
  • packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go (1 hunks)

Comment on lines +42 to +55
func ConnectDB() (*gorm.DB, error) {
sqlDB, err := sql.Open("sqlite", "test.db")
if err != nil {
return nil, err
}

db, err := gorm.Open(sqlite.Dialector{Conn: sqlDB}, &gorm.Config{})
if err != nil {
return nil, err
}

err = db.AutoMigrate(&User{},&Post{},&Tag{})
return db, err
}
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

Fix resource leak when GORM initialization fails.

If gorm.Open fails at line 48, the sqlDB connection opened at line 43 is never closed, leading to a resource leak.

Apply this diff to properly handle the resource:

 func ConnectDB() (*gorm.DB, error) {
 	sqlDB, err := sql.Open("sqlite", "test.db")
     if err != nil {
         return nil, err
     }
     
     db, err := gorm.Open(sqlite.Dialector{Conn: sqlDB}, &gorm.Config{})
     if err != nil {
+        sqlDB.Close()
         return nil, err
     }
     
-    err = db.AutoMigrate(&User{},&Post{},&Tag{})
+    err = db.AutoMigrate(&User{}, &Post{}, &Tag{})
     return db, err
 }
📝 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
func ConnectDB() (*gorm.DB, error) {
sqlDB, err := sql.Open("sqlite", "test.db")
if err != nil {
return nil, err
}
db, err := gorm.Open(sqlite.Dialector{Conn: sqlDB}, &gorm.Config{})
if err != nil {
return nil, err
}
err = db.AutoMigrate(&User{},&Post{},&Tag{})
return db, err
}
func ConnectDB() (*gorm.DB, error) {
sqlDB, err := sql.Open("sqlite", "test.db")
if err != nil {
return nil, err
}
db, err := gorm.Open(sqlite.Dialector{Conn: sqlDB}, &gorm.Config{})
if err != nil {
sqlDB.Close()
return nil, err
}
err = db.AutoMigrate(&User{}, &Post{}, &Tag{})
return db, err
}
🤖 Prompt for AI Agents
In packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go
around lines 42 to 55, if gorm.Open fails the previously opened sqlDB is never
closed; update the error path so that when gorm.Open returns an error you call
sqlDB.Close() before returning the error (optionally capture/ignore Close
error), ensuring the SQL connection is not leaked. Keep the rest of the flow the
same (open sql, attempt gorm.Open, on gorm.Open error close sqlDB and return
nil, err; on success continue to AutoMigrate).

Comment on lines +58 to +63
func CreateUserWithPosts(db *gorm.DB, user *User) error {

result := db.Create(&user)

return result.Error
}
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 | 🔴 Critical

Fix double-pointer bug in Create call.

Line 60 takes the address of user, which is already a *User pointer. This creates a **User (pointer to pointer), which will cause GORM to fail or behave incorrectly.

Apply this diff:

 func CreateUserWithPosts(db *gorm.DB, user *User) error {
     
-	result := db.Create(&user)
+	result := db.Create(user)
 	
 	return result.Error
 }
📝 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
func CreateUserWithPosts(db *gorm.DB, user *User) error {
result := db.Create(&user)
return result.Error
}
func CreateUserWithPosts(db *gorm.DB, user *User) error {
result := db.Create(user)
return result.Error
}
🤖 Prompt for AI Agents
In packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go
around lines 58 to 63, db.Create is called with &user which makes a **User
(pointer-to-pointer); change the call to pass the existing *User directly
(db.Create(user)) so GORM receives a *User, not **User, and return result.Error
as before.

Comment on lines +75 to +114
// CreatePostWithTags creates a new post with specified tags
func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error {
tx := db.Begin()
if tx.Error != nil {
return tx.Error
}
ShouldRollback := true
defer func(){
if ShouldRollback{
tx.Rollback()
}
}()
var tags []Tag
for _, tagName := range tagNames{
var tag Tag
if err:= tx.Where("name = ?", tagName).First(&tag).Error; err!= nil{
if errors.Is(err, gorm.ErrRecordNotFound){
tag = Tag{Name: tagName}
if err:= tx.Create(&tag).Error; err!=nil{
tx.Rollback()
return err
}
} else {
tx.Rollback()
return err
}
}
tags = append(tags, tag)
}
post.Tags = tags
if err:= tx.Create(post).Error; err!= nil{
tx.Rollback()
return err
}
if err:= tx.Commit().Error; err!=nil{
return err
}
ShouldRollback = false
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.

🛠️ Refactor suggestion | 🟠 Major

Simplify transaction error handling and fix naming inconsistency.

The manual tx.Rollback() calls at lines 94, 98, and 106 are redundant since the deferred function will handle rollback when ShouldRollback is true. Additionally, the variable ShouldRollback (line 81) uses uppercase naming, which is inconsistent with shouldRollback (lowercase) used in AddTagsToPost at line 139.

Apply this diff to remove redundancy and ensure consistent naming:

 func CreatePostWithTags(db *gorm.DB, post *Post, tagNames []string) error {
 	tx := db.Begin()
 	if tx.Error != nil {
     return tx.Error
 }
-    ShouldRollback := true
+    shouldRollback := true
 	defer func(){
-	    if ShouldRollback{
+	    if shouldRollback{
 	        tx.Rollback()
 	    }
 	}()
 	var tags []Tag
 	for _, tagName := range tagNames{
 	    var tag Tag
 	    if err:= tx.Where("name = ?", tagName).First(&tag).Error; err!= nil{
 	        if errors.Is(err, gorm.ErrRecordNotFound){
 	            tag = Tag{Name: tagName}
 	            if err:= tx.Create(&tag).Error; err!=nil{
-	                tx.Rollback()
 	                return err
 	            }
 	        } else {
-	            tx.Rollback()
 	            return err
 	        }
 	    }
 	    tags = append(tags, tag)
 	}
 	post.Tags = tags
 	if err:= tx.Create(post).Error; err!= nil{
-	    tx.Rollback()
 	    return err
 	}
 	if err:= tx.Commit().Error; err!=nil{
 	    return err
 	}
-	ShouldRollback = false
+	shouldRollback = false
 	return nil
 }
🤖 Prompt for AI Agents
In packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go
around lines 75 to 114, remove the redundant explicit tx.Rollback() calls inside
the loop and error branches (lines ~94, ~98, ~106) and rename the
exported-looking variable ShouldRollback to shouldRollback to match the naming
used elsewhere; keep the deferred rollback logic (defer func(){ if
shouldRollback { tx.Rollback() } }()), return errors immediately without calling
tx.Rollback() when an error occurs, and set shouldRollback = false only after a
successful Commit() so the defer can handle rollbacks consistently.

Comment on lines +133 to +176
// AddTagsToPost adds tags to an existing post
func AddTagsToPost(db *gorm.DB, postID uint, tagNames []string) error {
tx := db.Begin()
if tx.Error != nil {
return tx.Error
}
shouldRollback:= true
defer func(){
if shouldRollback{
tx.Rollback()
}
}()
var tags []Tag
for _, tagName := range tagNames{
var tag Tag
if err:= tx.Where("name = ?", tagName).First(&tag).Error; err!= nil{
if errors.Is(err, gorm.ErrRecordNotFound){
tag = Tag{Name: tagName}
if err:= tx.Create(&tag).Error; err!=nil{
tx.Rollback()
return err
}
} else {
tx.Rollback()
return err
}
}
tags = append(tags, tag)
}
var post Post
if err:= tx.First(&post, postID).Error; err!= nil{
tx.Rollback()
return err
}
if err:= tx.Model(&post).Association("Tags").Append(tags); err!=nil{
tx.Rollback()
return err
}
if err:= tx.Commit().Error; err!=nil{
return err
}
shouldRollback = false
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.

🛠️ Refactor suggestion | 🟠 Major

Remove redundant rollback calls.

The manual tx.Rollback() calls at lines 152, 156, 164, and 168 are redundant since the deferred function will automatically handle rollback when shouldRollback is true.

Apply this diff to simplify error handling:

 func AddTagsToPost(db *gorm.DB, postID uint, tagNames []string) error {
     tx := db.Begin()
 	if tx.Error != nil {
     return tx.Error
 }
     shouldRollback:= true
 	defer func(){
 	    if shouldRollback{
 	        tx.Rollback()
 	    }
 	}()
 	var tags []Tag
 	for _, tagName := range tagNames{
 	    var tag Tag
 	    if err:= tx.Where("name = ?", tagName).First(&tag).Error; err!= nil{
 	        if errors.Is(err, gorm.ErrRecordNotFound){
 	            tag = Tag{Name: tagName}
 	            if err:= tx.Create(&tag).Error; err!=nil{
-	                tx.Rollback()
 	                return err
 	            }
 	        } else {
-	            tx.Rollback()
 	            return err
 	        }
 	    }
 	    tags = append(tags, tag)
 	}
 	var post Post
 	if err:= tx.First(&post, postID).Error; err!= nil{
-	   tx.Rollback()
 	   return err
 	}
 	if err:= tx.Model(&post).Association("Tags").Append(tags); err!=nil{
-	    tx.Rollback()
 	    return err
 	}
 	if err:= tx.Commit().Error; err!=nil{
 	    return err
 	}
 	shouldRollback = false
 	return nil
 }
🤖 Prompt for AI Agents
In packages/gorm/challenge-2-associations/submissions/grozdovk/solution.go
around lines 133 to 176, remove the redundant explicit tx.Rollback() calls
inside the error branches (lines ~152, ~156, ~164, ~168) and simply return the
error; keep the initial tx.Begin() error check and the deferred rollback that
checks shouldRollback, and only set shouldRollback = false after a successful
tx.Commit(); this simplifies error handling by letting the defer handle rollback
consistently while preserving current return semantics.

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.

1 participant